diff --git a/.dockerignore b/.dockerignore index 633d33ec32e0..1a33cf22fab7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,3 +21,15 @@ cloudstack/tools/docker/Dockerfile.smokedev .idea .git venv + +# Don't ship build outputs or local caches into the build context +**/target +**/node_modules +**/.mvn +**/*.iml +**/.DS_Store +docs +ui/dist +ui/node_modules +tools/marvin/dist +*.log diff --git a/.github/workflows/api-image.yml b/.github/workflows/api-image.yml new file mode 100644 index 000000000000..e88e48b8d1cc --- /dev/null +++ b/.github/workflows/api-image.yml @@ -0,0 +1,136 @@ +# Phase 5a: API / management-server container image build +# +# Triggers on any change to Java/Maven source — specifically anything that +# is NOT under ui/** (the UI image workflow handles that path). +# +# Images are tagged: ghcr.io//cloudstack-management:- +# ghcr.io//cloudstack-management:latest (main only) +# +# The root Dockerfile is a multi-stage Maven → JRE build. It is used +# unmodified here; the UI assets are NOT bundled because the Phase 5a goal +# is to ship two separate images. +# +# TODO: add image vulnerability scanning (e.g. trivy-action) before GA. +# TODO: add SBOM generation (anchore/sbom-action) and Cosign signing. + +name: "Phase 5a – API/management image" + +on: + push: + branches: + - "modernize-2026**" + - "main" + paths-ignore: + - "ui/**" + - ".github/workflows/ui-image.yml" + - "docs/**" + - "**.md" + pull_request: + branches: + - "modernize-2026**" + - "main" + paths-ignore: + - "ui/**" + - ".github/workflows/ui-image.yml" + - "docs/**" + - "**.md" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write # needed to push to GHCR + +env: + REGISTRY: ghcr.io + # TODO: set IMAGE_NAME to your fork org, e.g. ghcr.io/myorg/cloudstack-management + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/cloudstack-management + +jobs: + build-api-image: + name: "Build API/management Docker image" + # The Maven build is CPU/RAM heavy; ubuntu-22.04 has 7 GB RAM + 2 vCPU. + # If the build frequently OOMs consider switching to a larger runner. + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Compute image tags + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix=${{ github.ref_name }}-,format=short + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + + # ----------------------------------------------------------------------- + # Maven layer cache: re-uses ~/.m2 from a previous run so the Buildx + # cache-to/from covers the Docker layer that downloads Maven deps. + # ----------------------------------------------------------------------- + + # ----------------------------------------------------------------------- + # Pre-merge: image is BUILT (cache populated) but NOT pushed. + # Post-merge: the push step is also enabled. + # ----------------------------------------------------------------------- + + # Pre-merge block — always runs (build + cache, no push) + - name: Build API image (PR — no push) + if: github.event_name == 'pull_request' + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + # Exclude the ui/ source tree from the Docker build context to keep + # the context transfer fast. The API image does not need UI assets. + # TODO: add a .dockerignore entry for ui/ to enforce this at repo level. + push: false + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + # GHA cache keyed on branch so PRs benefit from branch history. + cache-from: | + type=gha,scope=api-image-${{ github.ref_name }} + type=gha,scope=api-image-main + cache-to: type=gha,mode=max,scope=api-image-${{ github.ref_name }} + # Pass build args that the Dockerfile already supports for tuning. + build-args: | + MAVEN_OPTS=-Xmx1g -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + + # Post-merge block — runs on push to tracked branches, also logs in + - name: Log in to GHCR + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + # TODO: configure GHCR_TOKEN secret in repo settings + password: ${{ secrets.GHCR_TOKEN }} + + - name: Build and push API image (post-merge) + if: github.event_name == 'push' + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: | + type=gha,scope=api-image-${{ github.ref_name }} + type=gha,scope=api-image-main + cache-to: type=gha,mode=max,scope=api-image-${{ github.ref_name }} + build-args: | + MAVEN_OPTS=-Xmx1g -XX:+TieredCompilation -XX:TieredStopAtLevel=1 + + - name: Image digest + if: github.event_name == 'push' + run: echo "Pushed API image digest ${{ steps.meta.outputs.version }}" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c33a1313436..0dd7d635ebe1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,6 +28,8 @@ permissions: jobs: build: + # Upstream-only: requires shapeblue/cloudstack-nonoss (proprietary). Forks use fork-ci.yml. + if: github.repository == 'apache/cloudstack' runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v6 diff --git a/.github/workflows/fork-ci.yml b/.github/workflows/fork-ci.yml new file mode 100644 index 000000000000..41bdc7dc38dd --- /dev/null +++ b/.github/workflows/fork-ci.yml @@ -0,0 +1,153 @@ +# Fork CI: build + unit test on every push and PR. +# Runs without -Dnoredist to avoid the proprietary cloudstack-nonoss dependency. + +name: Fork CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + build-and-test: + name: Build & Test (Java ${{ matrix.java }}) + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + java: [ '17' ] + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + cache: 'maven' + + - name: Show environment + run: | + java -version + mvn -v + free -m + nproc + + - name: Build (skip tests) + run: mvn -B -ntp install -DskipTests -T1C + + - name: Run unit tests with coverage + run: mvn -B -ntp test -fae -T1C -P quality + + - name: Generate aggregate JaCoCo report + if: matrix.java == '17' + run: | + find . -path '*/target/jacoco.exec' -type f | head -5 + mvn -B -ntp -pl :cloud-utils jacoco:report || true + + - name: Upload JaCoCo coverage reports + if: always() && matrix.java == '17' + uses: actions/upload-artifact@v4 + with: + name: jacoco-reports + path: '**/target/site/jacoco/**' + retention-days: 14 + if-no-files-found: ignore + + - name: Upload surefire reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: surefire-reports-jdk${{ matrix.java }} + path: '**/target/surefire-reports/**' + retention-days: 14 + + - name: Publish test results + if: always() + uses: mikepenz/action-junit-report@v4 + with: + report_paths: '**/target/surefire-reports/TEST-*.xml' + fail_on_failure: false + require_tests: false + check_name: Unit Tests (Java ${{ matrix.java }}) + + quality: + name: Quality checks (advisory) + runs-on: ubuntu-24.04 + # Quality checks are advisory — they upload reports but never fail the build. + # As code quality improves we'll progressively raise the bar. + continue-on-error: true + needs: build-and-test + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: Compile (cached) + run: mvn -B -ntp install -DskipTests -T1C + + - name: SpotBugs analysis + run: mvn -B -ntp spotbugs:spotbugs -DskipTests -fae || true + + - name: PMD analysis + run: mvn -B -ntp pmd:pmd -DskipTests -fae || true + + - name: Upload SpotBugs reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: spotbugs-reports + path: '**/target/spotbugsXml.xml' + retention-days: 14 + if-no-files-found: ignore + + - name: Upload PMD reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: pmd-reports + path: '**/target/pmd.xml' + retention-days: 14 + if-no-files-found: ignore + + security: + name: Dependency vulnerability scan + runs-on: ubuntu-24.04 + continue-on-error: true + needs: build-and-test + steps: + - uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: OWASP Dependency-Check + run: mvn -B -ntp -P quality org.owasp:dependency-check-maven:check -DskipTests -DfailBuildOnCVSS=11 || true + + - name: Upload dependency-check report + if: always() + uses: actions/upload-artifact@v4 + with: + name: dependency-check-report + path: '**/target/dependency-check-report.*' + retention-days: 30 + if-no-files-found: ignore diff --git a/.github/workflows/ui-image.yml b/.github/workflows/ui-image.yml new file mode 100644 index 000000000000..c4844ec5f8e8 --- /dev/null +++ b/.github/workflows/ui-image.yml @@ -0,0 +1,123 @@ +# Phase 5a: UI container image build +# +# Triggers on any change under ui/** — completely independent of the +# Java/Maven build so it can finish in ~3 minutes on a small runner. +# +# Images are tagged: ghcr.io//cloudstack-ui:- +# ghcr.io//cloudstack-ui:latest (main only) +# +# TODO: replace the GHCR org below with your fork's GitHub org once you +# have configured GHCR_TOKEN as a repo secret. + +name: "Phase 5a – UI image" + +on: + push: + branches: + - "modernize-2026**" + - "main" + paths: + - "ui/**" + - ".github/workflows/ui-image.yml" + pull_request: + branches: + - "modernize-2026**" + - "main" + paths: + - "ui/**" + - ".github/workflows/ui-image.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + packages: write # needed to push to GHCR + +env: + REGISTRY: ghcr.io + # TODO: set IMAGE_NAME to your fork org, e.g. ghcr.io/myorg/cloudstack-ui + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/cloudstack-ui + +jobs: + build-ui-image: + name: "Build UI Docker image" + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + # ----------------------------------------------------------------------- + # Upgrade the UI Dockerfile's Node base to 20 at build time via + # --build-arg so we do not have to touch the committed Dockerfile yet. + # TODO: update ui/Dockerfile FROM node:14-bullseye → node:20-bookworm + # once this workflow proves stable, then drop the build-arg. + # ----------------------------------------------------------------------- + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Compute image tags + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: | + # branch + short-SHA on every push/PR + type=sha,prefix=${{ github.ref_name }}-,format=short + # semver tags from Git tags, e.g. v4.22.0 + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + # "latest" only when pushing to main + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + + # ----------------------------------------------------------------------- + # Pre-merge: image is BUILT (cache populated) but NOT pushed. + # Post-merge: the push step is also enabled. + # ----------------------------------------------------------------------- + + # Pre-merge block — always runs (build + cache, no push) + - name: Build UI image (PR — no push) + if: github.event_name == 'pull_request' + uses: docker/build-push-action@v5 + with: + context: ./ui + file: ./ui/Dockerfile + push: false + # Override the node base image version via build-arg + # TODO: drop once ui/Dockerfile is updated to node:20 + build-args: | + NODE_BASE=node:20-bookworm + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=ui-image + cache-to: type=gha,mode=max,scope=ui-image + + # Post-merge block — runs on push to tracked branches, also logs in + - name: Log in to GHCR + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + # TODO: configure GHCR_TOKEN secret in repo settings + password: ${{ secrets.GHCR_TOKEN }} + + - name: Build and push UI image (post-merge) + if: github.event_name == 'push' + uses: docker/build-push-action@v5 + with: + context: ./ui + file: ./ui/Dockerfile + push: true + build-args: | + NODE_BASE=node:20-bookworm + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=ui-image + cache-to: type=gha,mode=max,scope=ui-image + + - name: Image digest + if: github.event_name == 'push' + run: echo "Pushed UI image digest ${{ steps.meta.outputs.version }}" diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml new file mode 100644 index 000000000000..43990c90b0cd --- /dev/null +++ b/.github/workflows/web.yml @@ -0,0 +1,55 @@ +name: web + +on: + pull_request: + paths: + - 'web/**' + - '.github/workflows/web.yml' + - 'docker-compose.yml' + push: + branches: + - modernize-2026 + paths: + - 'web/**' + - '.github/workflows/web.yml' + - 'docker-compose.yml' + +permissions: + contents: read + +jobs: + build: + name: Lint, typecheck, build + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./web + steps: + - uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: web/package-lock.json + + - name: Install + run: npm ci --no-audit --no-fund + + - name: Lint + run: npm run lint + + - name: Typecheck + run: npm run typecheck + + - name: Build + run: npm run build + + - name: Check Docker Compose config + working-directory: . + run: docker compose -f docker-compose.yml config + + - name: Build Docker image + working-directory: . + run: docker build -t cloudstack-web ./web diff --git a/.gitignore b/.gitignore index abaef83e4555..915fe71a8e95 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,13 @@ waf-* #.* !.gitignore + +# Claude Code per-developer settings (contains per-user auth allowlist) +.claude/ + +# Local linked worktrees for AI-assisted slice work +.worktrees/ + +# Playwright browser-test output +web/test-results/ +web/playwright-report/ diff --git a/.java-version b/.java-version index 2dbc24b32d3c..aabe6ec3909c 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -11.0 +21 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000000..506530d052b2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,99 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +# CloudStack management server — production runtime image. +# +# Build: +# docker build -t cloudstack-management:dev . +# +# Run (requires external MySQL): +# docker run --rm -p 8080:8080 -p 8443:8443 \ +# -e CLOUDSTACK_LOG_FORMAT=json \ +# -e DB_HOST=mysql.example.internal \ +# -e DB_USER=cloud -e DB_PASSWORD=cloud \ +# cloudstack-management:dev +# +# Or use docker compose for a one-command local stack: +# docker compose up + +# ----------------------------------------------------------------------------- +# Build stage: compile the management server WAR + dependencies +# ----------------------------------------------------------------------------- +FROM eclipse-temurin:21-jdk-noble AS build + +RUN apt-get update && apt-get install -y --no-install-recommends \ + maven \ + git \ + python3 \ + python3-mysql.connector \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /src +COPY pom.xml ./ +COPY . ./ + +# -Dnoredist=false: skip noredist proprietary bits; -P developer,systemvm: standard build profile +RUN mvn -B -ntp install -DskipTests -P developer,systemvm -T1C + +# ----------------------------------------------------------------------------- +# Runtime stage: minimal Java image with the built jars +# ----------------------------------------------------------------------------- +FROM eclipse-temurin:21-jre-noble + +LABEL org.opencontainers.image.title="CloudStack Management Server" \ + org.opencontainers.image.description="Apache CloudStack management server (fork)" \ + org.opencontainers.image.licenses="Apache-2.0" \ + org.opencontainers.image.source="https://github.com/d4m14ndx/cloudstack" + +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ + tini \ + && rm -rf /var/lib/apt/lists/* \ + && groupadd -r cloud --gid 1000 \ + && useradd -r -g cloud --uid 1000 --shell /usr/sbin/nologin --home-dir /var/lib/cloudstack cloud \ + && mkdir -p /etc/cloudstack/management /var/log/cloudstack/management /var/lib/cloudstack \ + && chown -R cloud:cloud /etc/cloudstack /var/log/cloudstack /var/lib/cloudstack + +# Copy the built artifacts +COPY --from=build --chown=cloud:cloud /src/client/target/cloud-client-ui-*.jar /usr/share/cloudstack-management/cloud-client-ui.jar +COPY --from=build --chown=cloud:cloud /src/client/target/lib /usr/share/cloudstack-management/lib +COPY --from=build --chown=cloud:cloud /src/client/target/common/scripts /usr/share/cloudstack-management/scripts + +# Default server.properties — overridden by mounting your own at /etc/cloudstack/management/server.properties +COPY --from=build --chown=cloud:cloud /src/client/target/conf/server.properties /etc/cloudstack/management/server.properties +COPY --from=build --chown=cloud:cloud /src/client/target/conf/db.properties /etc/cloudstack/management/db.properties +COPY --from=build --chown=cloud:cloud /src/client/target/conf/log4j-cloud.xml /etc/cloudstack/management/log4j-cloud.xml + +USER cloud +WORKDIR /var/lib/cloudstack + +EXPOSE 8080 8443 + +# Defaults that should usually be overridden: +ENV JAVA_OPTS="-Xmx2g -Xms512m" \ + CLOUDSTACK_LOG_FORMAT=text \ + OTEL_SERVICE_NAME=cloudstack-management + +# Liveness probe target (HEALTHCHECK uses /health/live; readiness is for orchestrators) +HEALTHCHECK --interval=30s --timeout=5s --start-period=120s --retries=3 \ + CMD curl -fsS http://localhost:8080/client/health/live || exit 1 + +ENTRYPOINT ["/usr/bin/tini", "--"] +CMD ["sh", "-c", "exec java $JAVA_OPTS \ + -classpath /etc/cloudstack/management:/usr/share/cloudstack-management/cloud-client-ui.jar:/usr/share/cloudstack-management/lib/* \ + -Dlog4j.configurationFile=/etc/cloudstack/management/log4j-cloud.xml \ + -Djavax.net.ssl.trustStorePassword=vmops.com \ + --add-opens=java.base/java.lang=ALL-UNNAMED \ + --add-opens=java.base/java.util=ALL-UNNAMED \ + --add-exports=java.base/sun.security.x509=ALL-UNNAMED \ + --add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED \ + org.apache.cloudstack.ServerDaemon"] diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index ba4a3874664a..ce2e8e272e8c 100644 --- a/agent/conf/agent.properties +++ b/agent/conf/agent.properties @@ -172,6 +172,16 @@ hypervisor.type=kvm # This parameter specifies a directory on the host local storage for creating and hosting the config drives. #host.cache.location=/var/cache/cloud +# Enables TLS on the KVM image server transfer endpoint. +#image.server.tls.enabled=true + +# The IP address that the KVM image server listens on. +# If not set, the agent private IP address will be used. +#image.server.listen.address= + +# Path to the KVM image server Unix domain control socket. +#image.server.socket.path=/var/run/cloudstack/image-server.sock + # Sets the rolling maintenance hook scripts directory. # Default is null, however, can be used as /etc/cloudstack/agent/hooks.d #rolling.maintenance.hooks.dir= diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java index e69a7efdc9c7..5b4315a585df 100644 --- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java +++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java @@ -123,6 +123,27 @@ public class AgentProperties{ */ public static final Property LOCAL_STORAGE_PATH = new Property<>("local.storage.path", "/var/lib/libvirt/images/"); + /** + * Enables TLS on the KVM image server transfer endpoint.
+ * Data type: Boolean.
+ * Default value: true + */ + public static final Property IMAGE_SERVER_TLS_ENABLED = new Property<>("image.server.tls.enabled", true); + + /** + * The IP address that the KVM image server listens on.
+ * Data type: String.
+ * Default value: null + */ + public static final Property IMAGE_SERVER_LISTEN_ADDRESS = new Property<>("image.server.listen.address", null, String.class); + + /** + * Path to the KVM image server Unix domain control socket.
+ * Data type: String.
+ * Default value: /var/run/cloudstack/image-server.sock + */ + public static final Property IMAGE_SERVER_SOCKET_PATH = new Property<>("image.server.socket.path", "/var/run/cloudstack/image-server.sock"); + /** * Directory where Qemu sockets are placed.
* These sockets are for the Qemu Guest Agent and SSVM provisioning.
diff --git a/api/pom.xml b/api/pom.xml index c80c35593451..a2e89fbb4f33 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -42,8 +42,8 @@ ${cs.commons-math3.version} - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api org.apache.cloudstack diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 42395bf89992..25e1fb50aac5 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -282,6 +282,7 @@ public class EventTypes { // UserVO Events public static final String EVENT_USER_LOGIN = "USER.LOGIN"; public static final String EVENT_USER_LOGOUT = "USER.LOGOUT"; + public static final String EVENT_USER_IMPERSONATE = "USER.IMPERSONATE"; public static final String EVENT_USER_CREATE = "USER.CREATE"; public static final String EVENT_USER_DELETE = "USER.DELETE"; public static final String EVENT_USER_DISABLE = "USER.DISABLE"; @@ -639,6 +640,7 @@ public class EventTypes { public static final String EVENT_VM_BACKUP_OFFERING_ASSIGN = "BACKUP.OFFERING.ASSIGN"; public static final String EVENT_VM_BACKUP_OFFERING_REMOVE = "BACKUP.OFFERING.REMOVE"; public static final String EVENT_VM_BACKUP_CREATE = "BACKUP.CREATE"; + public static final String EVENT_VM_BACKUP_FINALIZE = "BACKUP.FINALIZE"; public static final String EVENT_VM_BACKUP_RESTORE = "BACKUP.RESTORE"; public static final String EVENT_VM_BACKUP_DELETE = "BACKUP.DELETE"; public static final String EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED = "BACKUP.OFFERING.BACKUPS.DEL"; diff --git a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java index 1f8741d3b7b2..f0678a631d77 100644 --- a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java +++ b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java @@ -52,7 +52,9 @@ public enum Functionality { public static final HypervisorType Parralels = new HypervisorType("Parralels"); public static final HypervisorType BareMetal = new HypervisorType("BareMetal"); public static final HypervisorType Simulator = new HypervisorType("Simulator", null, EnumSet.of(RootDiskSizeOverride, VmStorageMigration)); + /** @deprecated OVM (Oracle VM 2) hypervisor plugin has been removed. Retained for DB deserialization compatibility. */ public static final HypervisorType Ovm = new HypervisorType("Ovm", ImageFormat.RAW); + /** @deprecated OVM3 (Oracle VM 3) hypervisor plugin has been removed. Retained for DB deserialization compatibility. */ public static final HypervisorType Ovm3 = new HypervisorType("Ovm3", ImageFormat.RAW); public static final HypervisorType LXC = new HypervisorType("LXC"); public static final HypervisorType Custom = new HypervisorType("Custom", null, EnumSet.of(RootDiskSizeOverride)); diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index e41eb880ffd5..de3bb70adc23 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -176,8 +176,6 @@ public static class Provider { private static List supportedProviders = new ArrayList(); public static final Provider VirtualRouter = new Provider("VirtualRouter", false, false); - public static final Provider JuniperContrailRouter = new Provider("JuniperContrailRouter", false); - public static final Provider JuniperContrailVpcRouter = new Provider("JuniperContrailVpcRouter", false); public static final Provider JuniperSRX = new Provider("JuniperSRX", true); public static final Provider PaloAlto = new Provider("PaloAlto", true); public static final Provider F5BigIp = new Provider("F5BigIp", true); @@ -188,21 +186,10 @@ public static class Provider { public static final Provider SecurityGroupProvider = new Provider("SecurityGroupProvider", false); public static final Provider VPCVirtualRouter = new Provider("VpcVirtualRouter", false); public static final Provider None = new Provider("None", false); - // NiciraNvp is not an "External" provider, otherwise we get in trouble with NetworkServiceImpl.providersConfiguredForExternalNetworking - public static final Provider NiciraNvp = new Provider("NiciraNvp", false); public static final Provider InternalLbVm = new Provider("InternalLbVm", false); - public static final Provider CiscoVnmc = new Provider("CiscoVnmc", true); - // add new Ovs provider public static final Provider Ovs = new Provider("Ovs", false); public static final Provider Opendaylight = new Provider("Opendaylight", false); - public static final Provider BrocadeVcs = new Provider("BrocadeVcs", false); - // add GloboDns provider - public static final Provider GloboDns = new Provider("GloboDns", true); - // add Big Switch Bcf Provider - public static final Provider BigSwitchBcf = new Provider("BigSwitchBcf", false); - //Add ConfigDrive provider public static final Provider ConfigDrive = new Provider("ConfigDrive", false); - //Add Tungsten Fabric provider public static final Provider Tungsten = new Provider("Tungsten", false); public static final Provider Nsx = new Provider("Nsx", false); diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index 53692f932a4e..13fafcf45212 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -182,11 +182,11 @@ Pair, Integer> listNetworkService long findPhysicalNetworkId(long zoneId, String tag, TrafficType trafficType); PhysicalNetworkTrafficType addTrafficTypeToPhysicalNetwork(Long physicalNetworkId, String trafficType, String isolationMethod, String xenLabel, String kvmLabel, String vmwareLabel, - String simulatorLabel, String vlan, String hypervLabel, String ovm3label); + String simulatorLabel, String vlan, String hypervLabel); PhysicalNetworkTrafficType getPhysicalNetworkTrafficType(Long id); - PhysicalNetworkTrafficType updatePhysicalNetworkTrafficType(Long id, String xenLabel, String kvmLabel, String vmwareLabel, String hypervLabel, String ovm3label); + PhysicalNetworkTrafficType updatePhysicalNetworkTrafficType(Long id, String xenLabel, String kvmLabel, String vmwareLabel, String hypervLabel); boolean deletePhysicalNetworkTrafficType(Long id); diff --git a/api/src/main/java/com/cloud/network/Networks.java b/api/src/main/java/com/cloud/network/Networks.java index 5f767686dc97..008e538a79a9 100644 --- a/api/src/main/java/com/cloud/network/Networks.java +++ b/api/src/main/java/com/cloud/network/Networks.java @@ -246,6 +246,29 @@ public static String getValue(String uriString) throws URISyntaxException { return getValue(uriString == null ? null : new URI(uriString)); } + /** + * Parses the VLAN identifier from a {@code vlan://N} URI string. + * + * @param vlanUri a URI string with the "vlan" scheme, e.g. {@code "vlan://100"} or {@code "vlan://untagged"} + * @return the VLAN identifier (the URI's authority part) + * @throws com.cloud.utils.exception.CloudRuntimeException if the URI is malformed, + * doesn't use the "vlan" scheme, or has no extractable VLAN value + */ + public static String parseVlanNumberFromUri(String vlanUri) { + try { + URI uri = new URI(vlanUri); + String vlanId = getValue(uri); + if (vlanId == null || !"vlan".equalsIgnoreCase(uri.getScheme())) { + throw new com.cloud.utils.exception.CloudRuntimeException( + "Vlan parameter : " + vlanUri + " is not in valid format"); + } + return vlanId; + } catch (URISyntaxException e) { + throw new com.cloud.utils.exception.CloudRuntimeException( + "Invalid vlan parameter: " + vlanUri + " can't get vlan number from it due to: " + e.getMessage()); + } + } + /** * encode a string into a BroadcastUri * @param candidate the input string diff --git a/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java b/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java index d3804cd29daf..8e765d78ac12 100644 --- a/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java +++ b/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java @@ -40,7 +40,5 @@ public interface PhysicalNetworkTrafficType extends InternalIdentity, Identity { String getHypervNetworkLabel(); - String getOvm3NetworkLabel(); - String getVlan(); } diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index 9e56bf4f17b2..33cc6da70812 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -130,4 +130,10 @@ public interface VmDetailConstants { String EXTERNAL_DETAIL_PREFIX = "External:"; String CLOUDSTACK_VM_DETAILS = "cloudstack.vm.details"; String CLOUDSTACK_VLAN = "cloudstack.vlan"; + + // KVM Checkpoints related + String ACTIVE_CHECKPOINT_ID = "active.checkpoint.id"; + String ACTIVE_CHECKPOINT_CREATE_TIME = "active.checkpoint.create.time"; + String LAST_CHECKPOINT_ID = "last.checkpoint.id"; + String LAST_CHECKPOINT_CREATE_TIME = "last.checkpoint.create.time"; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java index 4d4ead277e5d..5fe07e4d1139 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -260,6 +260,7 @@ public class ApiConstants { public static final String FOR_VIRTUAL_NETWORK = "forvirtualnetwork"; public static final String FOR_SYSTEM_VMS = "forsystemvms"; public static final String FOR_PROVIDER = "forprovider"; + public static final String FROM_CHECKPOINT_ID = "fromcheckpointid"; public static final String FULL_PATH = "fullpath"; public static final String GATEWAY = "gateway"; public static final String IP6_GATEWAY = "ip6gateway"; @@ -332,6 +333,7 @@ public class ApiConstants { public static final String IS_2FA_VERIFIED = "is2faverified"; public static final String IS_2FA_MANDATED = "is2famandated"; + public static final String IS_ACTIVE = "isactive"; public static final String IS_ASYNC = "isasync"; public static final String IP_AVAILABLE = "ipavailable"; public static final String IP_LIMIT = "iplimit"; @@ -606,6 +608,7 @@ public class ApiConstants { public static final String TIMEZONE = "timezone"; public static final String TIMEZONEOFFSET = "timezoneoffset"; public static final String TENANT_NAME = "tenantname"; + public static final String TO_CHECKPOINT_ID = "tocheckpointid"; public static final String TOTAL = "total"; public static final String TOTAL_SUBNETS = "totalsubnets"; public static final String TOTAL_QUOTA = "totalquota"; @@ -819,7 +822,6 @@ public class ApiConstants { public static final String KVM_NETWORK_LABEL = "kvmnetworklabel"; public static final String VMWARE_NETWORK_LABEL = "vmwarenetworklabel"; public static final String HYPERV_NETWORK_LABEL = "hypervnetworklabel"; - public static final String OVM3_NETWORK_LABEL = "ovm3networklabel"; public static final String NETWORK_SERVICE_PROVIDER_ID = "nspid"; public static final String SERVICE_LIST = "servicelist"; public static final String CAN_ENABLE_INDIVIDUAL_SERVICE = "canenableindividualservice"; @@ -1176,9 +1178,6 @@ public class ApiConstants { public static final String METADATA = "metadata"; public static final String PHYSICAL_SIZE = "physicalsize"; public static final String CHAIN_SIZE = "chainsize"; - public static final String OVM3_POOL = "ovm3pool"; - public static final String OVM3_CLUSTER = "ovm3cluster"; - public static final String OVM3_VIP = "ovm3vip"; public static final String CLEAN_UP_DETAILS = "cleanupdetails"; public static final String CLEAN_UP_EXTERNAL_DETAILS = "cleanupexternaldetails"; public static final String CLEAN_UP_EXTRA_CONFIG = "cleanupextraconfig"; diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index 18c96c371591..1bb3e03664e6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java @@ -19,7 +19,7 @@ import java.net.InetAddress; import java.util.Map; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpSession; import com.cloud.domain.Domain; import com.cloud.exception.CloudAuthenticationException; @@ -33,6 +33,9 @@ public interface ApiServerService { public ResponseObject loginUser(HttpSession session, String username, String password, Long domainId, String domainPath, InetAddress loginIpAddress, Map requestParameters) throws CloudAuthenticationException; + public ResponseObject createUserSessionToken(HttpSession session, UserAccount targetUser, InetAddress loginIpAddress, + Map requestParameters) throws CloudAuthenticationException; + public void logoutUser(long userId); public boolean verifyUser(Long userId); diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index 00b1bc310d5a..b3a527591c36 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.regex.Pattern; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.bgp.BGPService; import org.apache.cloudstack.acl.ProjectRoleService; @@ -39,6 +39,7 @@ import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.gpu.GpuService; import org.apache.cloudstack.network.RoutedIpv4Manager; @@ -320,6 +321,18 @@ public void setResponseObject(final Object responseObject) { _responseObject = responseObject; } + protected SuccessResponse setSuccessResponse() { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + return response; + } + + protected SuccessResponse setSuccessResponse(final boolean success) { + SuccessResponse response = setSuccessResponse(); + response.setSuccess(success); + return response; + } + public static String getDateString(final Date date) { if (date == null) { return ""; diff --git a/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticator.java b/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticator.java index 7e33b1347db3..5d9442137df9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticator.java +++ b/api/src/main/java/org/apache/cloudstack/api/auth/APIAuthenticator.java @@ -18,9 +18,9 @@ import org.apache.cloudstack.api.ServerApiException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; import java.util.List; import java.util.Map; import java.net.InetAddress; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/NetworkElementApiExecutor.java b/api/src/main/java/org/apache/cloudstack/api/command/NetworkElementApiExecutor.java new file mode 100644 index 000000000000..a04014b8a5b8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/NetworkElementApiExecutor.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.exception.CloudRuntimeException; + +public final class NetworkElementApiExecutor { + + private NetworkElementApiExecutor() { + } + + public static void execute(ApiOperation operation) { + try { + operation.execute(); + } catch (InvalidParameterValueException invalidParamExcp) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); + } catch (CloudRuntimeException runtimeExcp) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); + } + } + + public static T requireNonNull(T result, String failureMessage) { + if (result == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, failureMessage); + } + return result; + } + + public static void requireSuccess(boolean result, String failureMessage) { + if (!result) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, failureMessage); + } + } + + @FunctionalInterface + public interface ApiOperation { + void execute(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java index c207801e3640..e25f8c297549 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DeleteAccountCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.account; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java index f7f8bd974272..38c3464c18b7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.account; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.SecurityChecker.AccessType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/EnableAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/EnableAccountCmd.java index 7478bc8b8116..efd821a96e16 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/EnableAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/EnableAccountCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.account; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java index b6b975ae1ce7..026cb3d8722e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/UpdateAccountCmd.java @@ -19,7 +19,7 @@ import java.util.Collection; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java index 058650cf42c8..e46d1f78537d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/ImportRoleCmd.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.Role; import org.apache.cloudstack.acl.RoleType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java index 500a77f3d4fc..09536b1335cb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CreateImageTransferCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CreateImageTransferCmd.java new file mode 100644 index 000000000000..eedc583e9deb --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CreateImageTransferCmd.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.utils.EnumUtils; + +@APICommand(name = "createImageTransfer", + description = "Create image transfer for a disk in backup. This API is intended for testing only and is disabled by default.", + responseObject = ImageTransferResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class CreateImageTransferCmd extends BaseCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + description = "ID of the backup") + private Long backupId; + + @Parameter(name = ApiConstants.VOLUME_ID, + type = CommandType.UUID, + entityType = VolumeResponse.class, + required = true, + description = "ID of the disk/volume") + private Long volumeId; + + @Parameter(name = ApiConstants.DIRECTION, + type = CommandType.STRING, + required = true, + description = "Direction of the transfer: upload, download") + private String direction; + + @Parameter(name = ApiConstants.FORMAT, + type = CommandType.STRING, + description = "Format for the image transfer: raw/cow. 'raw' will create an NBD backend. 'cow' will use the File backend. " + + "For download, only the 'raw' format is supported. Default: raw") + private String format; + + public Long getBackupId() { + return backupId; + } + + public Long getVolumeId() { + return volumeId; + } + + public ImageTransfer.Direction getDirection() { + return ImageTransfer.Direction.valueOf(direction); + } + + public ImageTransfer.Format getFormat() { + if (format == null) { + return ImageTransfer.Format.raw; + } + return EnumUtils.getEnum(ImageTransfer.Format.class, format); + } + + @Override + public void execute() { + ImageTransferResponse response = kvmBackupExportService.createImageTransfer(this); + response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java index 08523712d12b..4a01cc8cf2d2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteBackupOfferingCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.admin.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteVmCheckpointCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteVmCheckpointCmd.java new file mode 100644 index 000000000000..c6bdd1903ba3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteVmCheckpointCmd.java @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "deleteVirtualMachineCheckpoint", + description = "Delete a VM checkpoint. This API is intended for testing only and is disabled by default.", + responseObject = SuccessResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class DeleteVmCheckpointCmd extends BaseCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + @Parameter(name = "checkpointid", + type = CommandType.STRING, + required = true, + description = "Checkpoint ID") + private String checkpointId; + + public Long getVmId() { + return vmId; + } + + public String getCheckpointId() { + return checkpointId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setCheckpointId(String checkpointId) { + this.checkpointId = checkpointId; + } + + @Override + public void execute() { + boolean result = kvmBackupExportService.deleteVmCheckpoint(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java new file mode 100644 index 000000000000..ef4cdc2b45a5 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; + +@APICommand(name = "finalizeBackup", + description = "Finalize a VM backup session. This API is intended for testing only and is disabled by default.", + responseObject = BackupResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class FinalizeBackupCmd extends BaseAsyncCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Inject + private BackupManager backupManager; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "ID of the backup") + private Long backupId; + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + @Override + public void execute() { + Backup backup = kvmBackupExportService.finalizeBackup(this); + + if (backup == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to finalize Backup"); + } + + BackupResponse response = backupManager.createBackupResponse(backup, null); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_FINALIZE; + } + + @Override + public String getEventDescription() { + return "Finalizing backup " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeImageTransferCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeImageTransferCmd.java new file mode 100644 index 000000000000..88b56061ddd7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeImageTransferCmd.java @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "finalizeImageTransfer", + description = "Finalize an image transfer. This API is intended for testing only and is disabled by default.", + responseObject = SuccessResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class FinalizeImageTransferCmd extends BaseCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ImageTransferResponse.class, + required = true, + description = "ID of the image transfer") + private Long imageTransferId; + + public Long getImageTransferId() { + return imageTransferId; + } + + @Override + public void execute() { + boolean result = kvmBackupExportService.finalizeImageTransfer(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java index 4cf27c561508..3584ade8722c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java index e60a598913f7..9b37b0d72140 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProviderOfferingsCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java index 17575076444d..9e162f63406b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListBackupProvidersCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListImageTransfersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListImageTransfersCmd.java new file mode 100644 index 000000000000..4a51a9db9bbc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListImageTransfersCmd.java @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "listImageTransfers", + description = "List image transfers for a backup. This API is intended for testing only and is disabled by default.", + responseObject = ImageTransferResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class ListImageTransfersCmd extends BaseListCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ImageTransferResponse.class, + description = "ID of the Image Transfer") + private Long id; + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + description = "ID of the backup") + private Long backupId; + + public Long getId() { + return id; + } + + public Long getBackupId() { + return backupId; + } + + @Override + public void execute() { + List responses = kvmBackupExportService.listImageTransfers(this); + ListResponse response = new ListResponse<>(); + response.setResponses(responses); + response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListVmCheckpointsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListVmCheckpointsCmd.java new file mode 100644 index 000000000000..55407cacca33 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListVmCheckpointsCmd.java @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.CheckpointResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "listVirtualMachineCheckpoints", + description = "List checkpoints for a VM. This API is intended for testing only and is disabled by default.", + responseObject = CheckpointResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class ListVmCheckpointsCmd extends BaseListCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + public Long getVmId() { + return vmId; + } + + @Override + public void execute() { + List responses = kvmBackupExportService.listVmCheckpoints(this); + ListResponse response = new ListResponse<>(); + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java new file mode 100644 index 000000000000..ff1f9e59a58d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java @@ -0,0 +1,125 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; + +@APICommand(name = "startBackup", + description = "Start a VM backup session using pull mode backup-begin on the KVM host. This API is intended for testing only and is disabled by default.", + responseObject = BackupResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class StartBackupCmd extends BaseAsyncCreateCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Inject + private BackupManager backupManager; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "the name of the backup") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the backup") + private String description; + + public Long getVmId() { + return vmId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + @Override + public void execute() { + try { + Backup backup = kvmBackupExportService.startBackup(this); + BackupResponse response = backupManager.createBackupResponse(backup, null); + + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.Backup; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public void create() { + Backup backup = kvmBackupExportService.createBackup(this); + + if (backup == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup"); + } + setEntityId(backup.getId()); + setEntityUuid(backup.getUuid()); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CREATE; + } + + @Override + public String getEventDescription() { + return "Starting backup for Instance " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java index 2f0dd6acd0e1..9546ce7d1d8a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java index 79dad4269c9b..e32d9fa02b99 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java index 13cf7550c938..e5fd9612784b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCAProvidersCmd.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java index feb69e167ac4..7fabb818138c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ListCaCertificateCmd.java @@ -19,7 +19,7 @@ import java.io.IOException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java index 6deaea22ac6c..f364ab95dce0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.admin.ca; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java index c2212442f4ba..75a3a811142d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/RevokeCertificateCmd.java @@ -19,7 +19,7 @@ import java.math.BigInteger; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java index d8fa2123d228..abc6613f3fb4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java @@ -65,7 +65,7 @@ public class AddClusterCmd extends BaseCmd { @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, - description = "Hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3,External") + description = "Hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,External") private String hypervisor; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, @@ -112,13 +112,6 @@ public class AddClusterCmd extends BaseCmd { description = "Name of virtual switch used for public traffic in the cluster. This would override zone wide traffic label setting.") private String vSwitchNamePublicTraffic; - @Parameter(name = ApiConstants.OVM3_POOL, type = CommandType.STRING, required = false, description = "Ovm3 native pooling enabled for cluster") - private String ovm3pool; - @Parameter(name = ApiConstants.OVM3_CLUSTER, type = CommandType.STRING, required = false, description = "Ovm3 native OCFS2 clustering enabled for cluster") - private String ovm3cluster; - @Parameter(name = ApiConstants.OVM3_VIP, type = CommandType.STRING, required = false, description = "Ovm3 vip to use for pool (and cluster)") - private String ovm3vip; - @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, type = CommandType.LIST, collectionType = CommandType.STRING, description = "comma separated list of storage access groups for the hosts in the cluster", @@ -138,16 +131,6 @@ public class AddClusterCmd extends BaseCmd { since = "4.21.0") protected Map externalDetails; - public String getOvm3Pool() { - return ovm3pool; - } - public String getOvm3Cluster() { - return ovm3cluster; - } - public String getOvm3Vip() { - return ovm3vip; - } - public String getVSwitchTypeGuestTraffic() { return vSwitchTypeGuestTraffic; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java index 00e7da6e37c1..f324c1279ef7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java @@ -35,7 +35,7 @@ import org.apache.cloudstack.cluster.ClusterDrsService; import org.apache.commons.collections.MapUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java index 69a6c1134058..5c6c9e879c35 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/GenerateClusterDrsPlanCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.cluster.ClusterDrsService; -import javax.inject.Inject; +import jakarta.inject.Inject; import static org.apache.cloudstack.cluster.ClusterDrsService.ClusterDrsMaxMigrations; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.java index d34805ae2e32..68d1dfc5b86e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClusterDrsPlanCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.cluster.ClusterDrsService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listClusterDrsPlan", description = "List DRS plans for a clusters", responseObject = ClusterDrsPlanResponse.class, since = "4.19.0", requestHasSensitiveInfo = false) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java index 9fe4e3f5cfc1..ed71ef53c7b2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java index c140de5aa01e..de52461d76cb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java index d1f22baf6604..d3790ff6581a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java @@ -19,7 +19,7 @@ import java.util.Collections; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java index 9a605ec4200d..30777a54fc56 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ListTemplateDirectDownloadCertificatesCmd.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; import org.apache.cloudstack.direct.download.DirectDownloadManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java index 3dfbbd940c40..2890e3bc655d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/ProvisionTemplateDirectDownloadCertificateCmd.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.direct.download.DirectDownloadManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "provisionTemplateDirectDownloadCertificate", description = "Provisions a host with a direct download certificate", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java index 1ad8f271cfcf..5954447cb864 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java @@ -42,7 +42,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java index ad440376a913..6ae0fb45e57c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java @@ -34,7 +34,7 @@ import org.apache.cloudstack.direct.download.DirectDownloadManager; import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java index cf02e6a56bf8..e0aab69f0bec 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.domain; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java index 124a84931548..7d3b63769ae2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.domain; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java index cb427e659495..d7429d5534d0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ConfigureHAForHostCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "configureHAForHost", description = "Configures HA for a host", responseObject = HostHAResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java index 63c657a9e454..63309fd09bf1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.ha.HAConfigManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "disableHAForCluster", description = "Disables HA cluster-wide", responseObject = SuccessResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java index b90f731ff565..0ef9e28e4d4b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "disableHAForHost", description = "Disables HA for a host", responseObject = HostHAResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java index 07a6fbd2b399..9659989fe18e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.ha.HAConfigManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "disableHAForZone", description = "Disables HA for a zone", responseObject = SuccessResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java index 635fba988c60..9a23ec7d0c19 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.ha.HAConfigManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "enableHAForCluster", description = "Enables HA cluster-wide", responseObject = SuccessResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java index 0bda19a7ad3c..c173ac0388d6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "enableHAForHost", description = "Enables HA for a host", responseObject = HostHAResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java index f6d0f62bb120..1a11aed98b00 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.ha.HAConfigManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "enableHAForZone", description = "Enables HA for a zone", responseObject = SuccessResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAProvidersCmd.java index 808e0b4e8019..f50975db125c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAProvidersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAProvidersCmd.java @@ -37,7 +37,7 @@ import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAResourcesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAResourcesCmd.java index c610a605277c..0158ae6c5224 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAResourcesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/ListHostHAResourcesCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.ha.HAConfigManager; import org.apache.cloudstack.ha.HAResource; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkDeviceCmd.java index e90a56a92abb..e7674a22269c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkDeviceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkDeviceCmd.java @@ -18,7 +18,7 @@ import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkDeviceCmd.java index 89a36d0b94f5..84a387fc3d92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkDeviceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkDeviceCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.network; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkDeviceCmd.java index ace635376eb4..1db9c145a463 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkDeviceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListNetworkDeviceCmd.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java index b9a729bc1b77..a26c6d5408ec 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "changeOutOfBandManagementPassword", description = "Changes out-of-band management interface password on the host and updates the interface configuration in CloudStack if the operation succeeds, else reverts the old password", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java index 4052539a51d7..e374ff939e6d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ConfigureOutOfBandManagementCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "configureOutOfBandManagement", description = "Configures a host's out-of-band management interface", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java index 5b0f3a802071..3f5e3a4eacca 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "disableOutOfBandManagementForCluster", description = "Disables out-of-band management for a cluster", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java index c70622134531..d0ec0b84713d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "disableOutOfBandManagementForHost", description = "Disables out-of-band management for a host", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java index 2f2750580516..6a7da0516f7b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "disableOutOfBandManagementForZone", description = "Disables out-of-band management for a zone", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java index 4419ed18ee59..a1bd36d4b1d3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "enableOutOfBandManagementForCluster", description = "Enables out-of-band management for a cluster", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java index b3f2e8aaf821..035287b29c72 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "enableOutOfBandManagementForHost", description = "Enables out-of-band management for a host", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java index 8f6eb2dc3500..29f0c1724c79 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java @@ -39,7 +39,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "enableOutOfBandManagementForZone", description = "Enables out-of-band management for a zone", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java index bba3fee6ceca..9de6cc9b0a92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java @@ -38,7 +38,7 @@ import org.apache.cloudstack.outofbandmanagement.OutOfBandManagement.PowerOperation; import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "issueOutOfBandManagementPowerAction", description = "Initiates the specified power action to the host's out-of-band management interface", responseObject = OutOfBandManagementResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/AddRegionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/AddRegionCmd.java index 3a93a2750429..6cb3b360d21c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/AddRegionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/AddRegionCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.region; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/RemoveRegionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/RemoveRegionCmd.java index 3ea323eebfba..f5cd7ce4d5df 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/RemoveRegionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/RemoveRegionCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.region; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/UpdateRegionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/UpdateRegionCmd.java index ec5bfdabf5ec..fc73d6d7533a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/UpdateRegionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/UpdateRegionCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.region; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java index b6833f097336..193780e7764a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmd.java @@ -20,7 +20,7 @@ import java.util.Date; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/StartRollingMaintenanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/StartRollingMaintenanceCmd.java index 3869b9cdf957..8be668ffff58 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/StartRollingMaintenanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/StartRollingMaintenanceCmd.java @@ -19,7 +19,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java index 474e13de32f3..57cc25fbff2b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java index ba0110f014a0..b1c3d02a1fec 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java index 094e87ae0058..84a76bc2c730 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListOvsElementsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListOvsElementsCmd.java index 5ed3ae0e208e..5cc572f862c0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListOvsElementsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListOvsElementsCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListVirtualRouterElementsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListVirtualRouterElementsCmd.java index 6b175b10ebd9..d2cf9545827c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListVirtualRouterElementsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ListVirtualRouterElementsCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java index 1d927ac5cbd6..b56d24dd163a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java @@ -26,7 +26,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.storage.browser.StorageBrowser; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.nio.file.Path; @APICommand(name = "downloadImageStoreObject", description = "Download object at a specified path on an image store.", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.java index 48fd25c99c65..b0ac5e49c112 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListImageStoreObjectsCmd.java @@ -25,7 +25,7 @@ import org.apache.cloudstack.storage.browser.DataStoreObjectResponse; import org.apache.cloudstack.storage.browser.StorageBrowser; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.nio.file.Path; @APICommand(name = "listImageStoreObjects", description = "Lists objects at specified path on an image store.", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.java index 4dac92a95723..6da2963a0981 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolObjectsCmd.java @@ -25,7 +25,7 @@ import org.apache.cloudstack.storage.browser.DataStoreObjectResponse; import org.apache.cloudstack.storage.browser.StorageBrowser; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.nio.file.Path; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java index 50abd953e63f..8a4752a297d0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java @@ -72,11 +72,6 @@ public class AddTrafficTypeCmd extends BaseAsyncCreateCmd { description = "The network name label of the physical device dedicated to this traffic on a Hyperv host") private String hypervLabel; - @Parameter(name = ApiConstants.OVM3_NETWORK_LABEL, - type = CommandType.STRING, - description = "The network name of the physical device dedicated to this traffic on an OVM3 host") - private String ovm3Label; - @Parameter(name = ApiConstants.VLAN, type = CommandType.STRING, description = "The VLAN id to be used for Management traffic by VMware host") private String vlan; @@ -117,10 +112,6 @@ public String getSimulatorLabel() { return null; } - public String getOvm3Label() { - return ovm3Label; - } - public void setVlan(String vlan) { this.vlan = vlan; } @@ -163,7 +154,7 @@ public void execute() { public void create() throws ResourceAllocationException { PhysicalNetworkTrafficType result = _networkService.addTrafficTypeToPhysicalNetwork(getPhysicalNetworkId(), getTrafficType(), getIsolationMethod(), getXenLabel(), getKvmLabel(), getVmwareLabel(), - getSimulatorLabel(), getVlan(), getHypervLabel(), getOvm3Label()); + getSimulatorLabel(), getVlan(), getHypervLabel()); if (result != null) { setEntityId(result.getId()); setEntityUuid(result.getUuid()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java index 29b06a3b5259..9ed691699b8d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java @@ -62,11 +62,6 @@ public class UpdateTrafficTypeCmd extends BaseAsyncCmd { description = "The network name label of the physical device dedicated to this traffic on a Hyperv host") private String hypervLabel; - @Parameter(name = ApiConstants.OVM3_NETWORK_LABEL, - type = CommandType.STRING, - description = "The network name of the physical device dedicated to this traffic on an OVM3 host") - private String ovm3Label; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -91,10 +86,6 @@ public String getHypervLabel() { return hypervLabel; } - public String getOvm3Label() { - return ovm3Label; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -106,7 +97,7 @@ public long getEntityOwnerId() { @Override public void execute() { - PhysicalNetworkTrafficType result = _networkService.updatePhysicalNetworkTrafficType(getId(), getXenLabel(), getKvmLabel(), getVmwareLabel(), getHypervLabel(), getOvm3Label()); + PhysicalNetworkTrafficType result = _networkService.updatePhysicalNetworkTrafficType(getId(), getXenLabel(), getKvmLabel(), getVmwareLabel(), getHypervLabel()); if (result != null) { TrafficTypeResponse response = _responseGenerator.createTrafficTypeResponse(result); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java index 01886187f9b9..e045de1b6799 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java index cc2bc0906a24..84460c2ab474 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java index 9141a9bccf8a..9b03a4e6c42c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java index aab20f108f9e..a2c8068a22c9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java index 3f5ce2415022..5717e2ceb67d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java index 3284dbafe7ca..a08d708fffd9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java @@ -21,7 +21,7 @@ import java.util.HashMap; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java index db7dcc3fb44f..f4326acbf6b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java @@ -42,7 +42,7 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "importVm", description = "Import virtual machine from a unmanaged host into CloudStack", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java index 94b547ff4267..2887615ed0cf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java @@ -37,7 +37,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.vm.ImportVmTasksManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listImportVmTasks", description = "List running import virtual machine tasks from a unmanaged hosts into CloudStack", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListUnmanagedInstancesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListUnmanagedInstancesCmd.java index 1d6285684471..bc1550775e76 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListUnmanagedInstancesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListUnmanagedInstancesCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.admin.vm; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java index f40f1c0cb4a9..21c4f72afdb8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListVmsForImportCmd.java @@ -37,7 +37,7 @@ import org.apache.cloudstack.vm.UnmanagedInstanceTO; import org.apache.cloudstack.vm.VmImportService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listVmsForImport", description = "Lists virtual machines on a unmanaged host", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java index 2c9f09dcd626..6c4ed73de9a9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java @@ -44,7 +44,7 @@ import org.apache.cloudstack.vm.UnmanagedVMsManager; import org.apache.commons.lang3.BooleanUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "unmanageVirtualMachine", description = "Unmanage a Guest Instance.", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java index 50f4b9c1fbe5..62d847ed8f7b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java @@ -40,7 +40,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.storage.volume.VolumeImportUnmanageService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "importVolume", description = "Import an unmanaged volume from a storage pool on a host into CloudStack", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ListVolumesForImportCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ListVolumesForImportCmd.java index dbe3d37e4069..6d094fd4a5c7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ListVolumesForImportCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ListVolumesForImportCmd.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.storage.volume.VolumeImportUnmanageService; import org.apache.cloudstack.storage.volume.VolumeOnStorageTO; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listVolumesForImport", description = "Lists unmanaged volumes on a storage pool", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java index ac573dd4ecb9..00abb6d66d59 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java @@ -40,7 +40,7 @@ import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.storage.volume.VolumeImportUnmanageService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "unmanageVolume", description = "Unmanage a volume on storage pool.", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java index 28cd642e1a41..833466c7503e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/AssignVirtualMachineToBackupOfferingCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java index ca60ea674fe3..19c3f0a6ebc4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java index 67ad7c71503f..6944baadba92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupScheduleCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java index faaf1735e1e9..4a633d4d1186 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 8dcf7574aed6..bb06c2a0d68d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java index d3c0f16d8d1c..1bb8679fc4fb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupOfferingsCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index 17bd06bfdd4b..7db8d5f670e3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.amazonaws.util.CollectionUtils; import org.apache.cloudstack.acl.RoleType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java index fb9c92f433e5..9f820d46c53f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java index dcf9f15b4dc5..4d2afb9db9c9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RemoveVirtualMachineFromBackupOfferingCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java index c29d117161f2..c0bef1a925d9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java index 4644687817df..b01fe27bca2e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreVolumeFromBackupAndAttachToVMCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.backup; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ACL; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java index 7caa4ce710ff..cace02f21a9d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java @@ -31,7 +31,7 @@ import org.apache.cloudstack.backup.BackupRepositoryService; import org.apache.cloudstack.context.CallContext; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "addBackupRepository", description = "Adds a backup repository to store NAS backups", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/DeleteBackupRepositoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/DeleteBackupRepositoryCmd.java index 912170eb4ca2..5b8769734e36 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/DeleteBackupRepositoryCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/DeleteBackupRepositoryCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.backup.BackupRepositoryService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "deleteBackupRepository", description = "delete a backup repository", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/ListBackupRepositoriesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/ListBackupRepositoriesCmd.java index 8293afb657d5..2e4a5adddff3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/ListBackupRepositoriesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/ListBackupRepositoriesCmd.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.backup.BackupRepository; import org.apache.cloudstack.backup.BackupRepositoryService; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java index 5ffd79e497ef..d17191f018cb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.backup.BackupRepositoryService; import org.apache.cloudstack.context.CallContext; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "updateBackupRepository", description = "Update a backup repository", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java index 41eaf36e4252..ac8b4c52a97e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java @@ -37,7 +37,7 @@ import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.ObjectUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Map; @APICommand(name = "createConsoleEndpoint", description = "Create a console endpoint to connect to a Instance console", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java index 774cd9d59fe7..a8155ddfa95d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.consoleproxy.ConsoleAccessManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; @APICommand(name = "listConsoleSessions", description = "Lists console sessions.", responseObject = ConsoleSessionResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java index 8566b413cc12..78cad13cc06f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.gui.theme.GuiThemeJoin; import org.apache.cloudstack.gui.theme.GuiThemeService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "createGuiTheme", description = "Creates a customized GUI theme for a set of Common Names (fixed or wildcard), a set of domain UUIDs, and/or a set of " + "account UUIDs.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java index 35a0a749aa94..04b111b8e3de 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.gui.theme.GuiTheme; import org.apache.cloudstack.gui.theme.GuiThemeService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listGuiThemes", description = "Lists GUI themes.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.User, RoleType.DomainAdmin, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java index 64164838eba4..e5a5365368ab 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.gui.theme.GuiTheme; import org.apache.cloudstack.gui.theme.GuiThemeService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "removeGuiTheme", description = "Removes an existing GUI theme.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java index daef2235ce89..a9f94d4fcd4f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.gui.theme.GuiThemeJoin; import org.apache.cloudstack.gui.theme.GuiThemeService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "updateGuiTheme", description = "Updates an existing GUI theme.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteSslCertCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteSslCertCmd.java index 887007e537e8..8c904f813b75 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteSslCertCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteSslCertCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.loadbalancer; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListSslCertsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListSslCertsCmd.java index 1bc300fdc69f..499e84d267e1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListSslCertsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListSslCertsCmd.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java index 0032b7a0acdf..cbe41433bf1a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UploadSslCertCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.loadbalancer; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ListRegionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ListRegionsCmd.java index 777f437851ca..9411ec2e463c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ListRegionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ListRegionsCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java index 8bb38d97c134..5366c3fcb396 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java index 2ecd8ef22e65..c3c1535a6701 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.region.ha.gslb; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java index b44b547463e5..f8874f8b71f4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.region.ha.gslb; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/ListGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/ListGlobalLoadBalancerRuleCmd.java index a4bd027fc976..2b5160710587 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/ListGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/ListGlobalLoadBalancerRuleCmd.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java index a0ec9a1296ab..aaf1eeeb134e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java @@ -19,7 +19,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java index a56672e29cac..41ca6a45b10f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.api.command.user.region.ha.gslb; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/DeleteSSHKeyPairCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/DeleteSSHKeyPairCmd.java index 4ed8664a79d7..37a6d3733dc5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/DeleteSSHKeyPairCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/DeleteSSHKeyPairCmd.java @@ -77,9 +77,7 @@ public Long getProjectId() { @Override public void execute() { boolean result = _mgr.deleteSSHKeyPair(this); - SuccessResponse response = new SuccessResponse(getCommandName()); - response.setSuccess(result); - setResponseObject(response); + setSuccessResponse(result); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java index 24290bc345e1..cb9fbcccde3d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java index 1ac0f27067b4..da91e60bfa69 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java index 595b611b5c0d..330a7fccf529 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java index 35f16a4dc2a0..1f086aa0e78b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java @@ -31,7 +31,7 @@ import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.storage.sharedfs.SharedFSService; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.event.EventTypes; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java index 8960aa3e4d40..3deeb8ef0fbb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSCmd.java index c52c691ac0b9..e76fb8aadec8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSCmd.java @@ -32,7 +32,7 @@ import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.storage.sharedfs.SharedFSService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listSharedFileSystems", responseObject= SharedFSResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSProvidersCmd.java index 940e07225cf9..71f64cd2b773 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSProvidersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ListSharedFSProvidersCmd.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RecoverSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RecoverSharedFSCmd.java index 6e5bbaa4d8a8..8a2ea90c4d32 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RecoverSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RecoverSharedFSCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java index 75565796caa4..2f44aaafff85 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java index d7440b532b31..09fc75e4025b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java index 3800b16289e7..72f5a14f5814 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.storage.sharedfs; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/UpdateSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/UpdateSharedFSCmd.java index daad6cc78c56..31ea5ef8558c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/UpdateSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/UpdateSharedFSCmd.java @@ -30,7 +30,7 @@ import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.storage.sharedfs.SharedFSService; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.user.Account; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java index 6350baefe9a5..3699a25219b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/CreateTagsCmd.java @@ -95,8 +95,7 @@ public void execute() { List tags = _taggedResourceService.createTags(getResourceIds(), getResourceType(), getTags(), getCustomer()); if (tags != null && !tags.isEmpty()) { - SuccessResponse response = new SuccessResponse(getCommandName()); - setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create resource tag(s)"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java index f8f319eba23d..7a8dddf74ef0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/tag/DeleteTagsCmd.java @@ -101,8 +101,7 @@ public void execute() { boolean success = _taggedResourceService.deleteTags(getResourceIds(), getResourceType(), getTags()); if (success) { - SuccessResponse response = new SuccessResponse(getCommandName()); - setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete tags"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java index 7e9bdd942ed7..d4756238e7f0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.api.response.VMScheduleResponse; import org.apache.cloudstack.vm.schedule.VMScheduleManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; @APICommand(name = "createVMSchedule", description = "Create Instance Schedule", responseObject = VMScheduleResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java index f34d07b045d9..097fc2d7844d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java @@ -33,7 +33,7 @@ import org.apache.cloudstack.vm.schedule.VMSchedule; import org.apache.cloudstack.vm.schedule.VMScheduleManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Collections; import java.util.List; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java index be94315abe76..825e0e2406a6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.vm.schedule.VMSchedule; import org.apache.cloudstack.vm.schedule.VMScheduleManager; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "listVMSchedule", description = "List Instance Schedules.", responseObject = VMScheduleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java index b7222944fe07..2ba4dc64ad5d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.vm.schedule.VMSchedule; import org.apache.cloudstack.vm.schedule.VMScheduleManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; @APICommand(name = "updateVMSchedule", description = "Update Instance Schedule.", responseObject = VMScheduleResponse.class, diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/DeleteVMGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/DeleteVMGroupCmd.java index b07084a273c5..93069b581396 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/DeleteVMGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/DeleteVMGroupCmd.java @@ -69,8 +69,7 @@ public long getEntityOwnerId() { public void execute() { boolean result = _userVmService.deleteVmGroup(this); if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete Instance group"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java index b855bfe40b8d..45a00cbc4de5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -127,6 +127,18 @@ public class BackupResponse extends BaseResponse { @Param(description = "Indicates whether the VM from which the backup was taken is expunged or not", since = "4.22.0") private Boolean isVmExpunged; + @SerializedName(ApiConstants.FROM_CHECKPOINT_ID) + @Param(description = "Previous active checkpoint ID for incremental backups", since = "4.23.0") + private String fromCheckpointId; + + @SerializedName(ApiConstants.TO_CHECKPOINT_ID) + @Param(description = "Next checkpoint ID for incremental backups", since = "4.23.0") + private String toCheckpointId; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "Host ID where the backup is running", since = "4.23.0") + private String hostId; + public String getId() { return id; } @@ -314,4 +326,28 @@ public void setVmOfferingRemoved(Boolean vmOfferingRemoved) { public void setVmExpunged(Boolean isVmExpunged) { this.isVmExpunged = isVmExpunged; } + + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public void setFromCheckpointId(String fromCheckpointId) { + this.fromCheckpointId = fromCheckpointId; + } + + public String getToCheckpointId() { + return toCheckpointId; + } + + public void setToCheckpointId(String toCheckpointId) { + this.toCheckpointId = toCheckpointId; + } + + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CheckpointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CheckpointResponse.java new file mode 100644 index 000000000000..7274aaff570b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/CheckpointResponse.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class CheckpointResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the checkpoint ID") + private String id; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the checkpoint creation time") + private Date created; + + @SerializedName(ApiConstants.IS_ACTIVE) + @Param(description = "whether this is the active checkpoint") + private Boolean isActive; + + public void setId(String id) { + this.id = id; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java index e73cd3876a92..1582d731d08f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java @@ -85,10 +85,6 @@ public class ClusterResponse extends BaseResponseWithAnnotations { @Param(description = "The memory overcommit ratio of the cluster") private String memoryovercommitratio; - @SerializedName("ovm3vip") - @Param(description = "Ovm3 VIP to use for pooling and/or clustering") - private String ovm3vip; - @SerializedName(ApiConstants.RESOURCE_DETAILS) @Param(description = "Meta data associated with the zone (key/value pairs)") private Map resourceDetails; @@ -229,14 +225,6 @@ public String getMemoryOvercommitRatio() { return memoryovercommitratio; } - public void setOvm3Vip(String ovm3vip) { - this.ovm3vip = ovm3vip; - } - - public String getOvm3Vip() { - return ovm3vip; - } - public void setResourceDetails(Map details) { if (details == null) { return; @@ -270,14 +258,6 @@ public void setMemoryovercommitratio(String memoryovercommitratio) { this.memoryovercommitratio = memoryovercommitratio; } - public String getOvm3vip() { - return ovm3vip; - } - - public void setOvm3vip(String ovm3vip) { - this.ovm3vip = ovm3vip; - } - public void setCapacities(List capacities) { this.capacities = capacities; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java new file mode 100644 index 000000000000..630d9191759d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.ImageTransfer; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ImageTransfer.class) +public class ImageTransferResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the image transfer") + private String id; + + @SerializedName(ApiConstants.BACKUP_ID) + @Param(description = "the backup ID") + private String backupId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "the VM ID") + private String vmId; + + @SerializedName(ApiConstants.VOLUME_ID) + @Param(description = "the disk/volume ID") + private String diskId; + + @SerializedName(ApiConstants.DEVICE_NAME) + @Param(description = "the device name (vda, vdb, etc)") + private String deviceName; + + @SerializedName("transferurl") + @Param(description = "the transfer URL") + private String transferUrl; + + @SerializedName("signedticketid") + @Param(description = "the signed ticket ID used to authorize the image transfer") + private String signedTicketId; + + @SerializedName("phase") + @Param(description = "the transfer phase") + private String phase; + + @SerializedName(ApiConstants.DIRECTION) + @Param(description = "the image transfer direction: upload / download") + private String direction; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the date created") + private Date created; + + public void setId(String id) { + this.id = id; + } + + public void setBackupId(String backupId) { + this.backupId = backupId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public void setDiskId(String diskId) { + this.diskId = diskId; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } + + public void setSignedTicketId(String signedTicketId) { + this.signedTicketId = signedTicketId; + } + + public void setPhase(String phase) { + this.phase = phase; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java index 2b8af97f160e..c3230625d2d7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java @@ -64,10 +64,6 @@ public class TrafficTypeResponse extends BaseResponse { @Param(description = "isolation methods for the physical network traffic") private String isolationMethods; - @SerializedName(ApiConstants.OVM3_NETWORK_LABEL) - @Param(description = "The Network name of the physical device dedicated to this traffic on an OVM3 host") - private String ovm3NetworkLabel; - @Override public String getObjectId() { return this.id; @@ -129,14 +125,6 @@ public String getVmwareLabel() { return vmwareNetworkLabel; } - public String getOvm3Label() { - return ovm3NetworkLabel; - } - - public void setOvm3Label(String ovm3Label) { - this.ovm3NetworkLabel = ovm3Label; - } - public String getIsolationMethods() { return isolationMethods; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserSessionTokenResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserSessionTokenResponse.java new file mode 100644 index 000000000000..66ba64fa0222 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserSessionTokenResponse.java @@ -0,0 +1,20 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +public class UserSessionTokenResponse extends LoginCmdResponse { +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java index 951af9180e7f..2d68f18b953f 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -30,8 +30,16 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity { + String getFromCheckpointId(); + + String getToCheckpointId(); + + Long getCheckpointCreateTime(); + + Long getHostId(); + enum Status { - Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged + Allocated, Queued, BackingUp, ReadyForImageTransfer, FinalizingImageTransfer, BackedUp, Error, Failed, Restoring, Removed, Expunged } class Metric { diff --git a/api/src/main/java/org/apache/cloudstack/backup/ImageTransfer.java b/api/src/main/java/org/apache/cloudstack/backup/ImageTransfer.java new file mode 100644 index 000000000000..9aaf5cee6911 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/ImageTransfer.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface ImageTransfer extends ControlledEntity, InternalIdentity { + enum Direction { + upload, download + } + + enum Format { + raw, cow + } + + enum Backend { + nbd, file + } + + enum Phase { + initializing, transferring, finished, failed + } + + long getDataCenterId(); + + String getUuid(); + + Long getBackupId(); + + long getVolumeId(); + + long getHostId(); + + String getTransferUrl(); + + Phase getPhase(); + + Direction getDirection(); + + Backend getBackend(); + + String getSignedTicketId(); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/KVMBackupExportService.java b/api/src/main/java/org/apache/cloudstack/backup/KVMBackupExportService.java new file mode 100644 index 000000000000..3b619eb662cb --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/KVMBackupExportService.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import java.util.List; + +import org.apache.cloudstack.api.command.admin.backup.CreateImageTransferCmd; +import org.apache.cloudstack.api.command.admin.backup.DeleteVmCheckpointCmd; +import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd; +import org.apache.cloudstack.api.command.admin.backup.FinalizeImageTransferCmd; +import org.apache.cloudstack.api.command.admin.backup.ListImageTransfersCmd; +import org.apache.cloudstack.api.command.admin.backup.ListVmCheckpointsCmd; +import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; +import org.apache.cloudstack.api.response.CheckpointResponse; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +import com.cloud.utils.component.PluggableService; + +public interface KVMBackupExportService extends Configurable, PluggableService { + + ConfigKey ImageTransferIdleTimeoutSeconds = new ConfigKey<>("Advanced", Integer.class, + "image.transfer.idle.timeout.seconds", + "600", + "Seconds since last completed HTTP request to an image transfer before the image server unregisters it (idle timeout).", + true, ConfigKey.Scope.Zone); + + ConfigKey ExposeKVMBackupExportServiceApis = new ConfigKey<>("Advanced", Boolean.class, + "expose.kvm.backup.export.service.apis", + "false", + "Enable to expose APIs for testing the KVM Backup Export Service.", + false, ConfigKey.Scope.Global); + + Backup createBackup(StartBackupCmd cmd); + + Backup startBackup(StartBackupCmd cmd); + + Backup finalizeBackup(FinalizeBackupCmd cmd); + + ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd); + + ImageTransfer createImageTransfer(long volumeId, Long backupId, ImageTransfer.Direction direction, ImageTransfer.Format format); + + boolean cancelImageTransfer(long imageTransferId); + + boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd); + + boolean finalizeImageTransfer(long imageTransferId); + + List listImageTransfers(ListImageTransfersCmd cmd); + + List listVmCheckpoints(ListVmCheckpointsCmd cmd); + + boolean deleteVmCheckpoint(DeleteVmCheckpointCmd cmd); + + List listCompatibleDataCenterIds(); +} diff --git a/api/src/main/java/org/apache/cloudstack/context/CallContextListener.java b/api/src/main/java/org/apache/cloudstack/context/CallContextListener.java index ab9a8c30046b..cd56bbfa887d 100644 --- a/api/src/main/java/org/apache/cloudstack/context/CallContextListener.java +++ b/api/src/main/java/org/apache/cloudstack/context/CallContextListener.java @@ -18,8 +18,8 @@ */ package org.apache.cloudstack.context; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.managed.context.ManagedContextListener; diff --git a/api/src/main/java/org/apache/cloudstack/context/LogContextListener.java b/api/src/main/java/org/apache/cloudstack/context/LogContextListener.java index 6fc1beb0b87d..051f1a7a2cbd 100644 --- a/api/src/main/java/org/apache/cloudstack/context/LogContextListener.java +++ b/api/src/main/java/org/apache/cloudstack/context/LogContextListener.java @@ -16,8 +16,8 @@ // under the License. package org.apache.cloudstack.context; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.managed.context.ManagedContextListener; diff --git a/api/src/main/java/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java b/api/src/main/java/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java index b34935611025..84bb97470e12 100644 --- a/api/src/main/java/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java +++ b/api/src/main/java/org/apache/cloudstack/network/ExternalNetworkDeviceManager.java @@ -43,11 +43,7 @@ public static class NetworkDevice { public static final NetworkDevice F5BigIpLoadBalancer = new NetworkDevice("F5BigIpLoadBalancer", Network.Provider.F5BigIp.getName()); public static final NetworkDevice JuniperSRXFirewall = new NetworkDevice("JuniperSRXFirewall", Network.Provider.JuniperSRX.getName()); public static final NetworkDevice PaloAltoFirewall = new NetworkDevice("PaloAltoFirewall", Network.Provider.PaloAlto.getName()); - public static final NetworkDevice NiciraNvp = new NetworkDevice("NiciraNvp", Network.Provider.NiciraNvp.getName()); - public static final NetworkDevice CiscoVnmc = new NetworkDevice("CiscoVnmc", Network.Provider.CiscoVnmc.getName()); public static final NetworkDevice OpenDaylightController = new NetworkDevice("OpenDaylightController", Network.Provider.Opendaylight.getName()); - public static final NetworkDevice BrocadeVcs = new NetworkDevice("BrocadeVcs", Network.Provider.BrocadeVcs.getName()); - public static final NetworkDevice GloboDns = new NetworkDevice("GloboDns", Network.Provider.GloboDns.getName()); public NetworkDevice(String deviceName, String ntwkServiceprovider) { _name = deviceName; diff --git a/api/src/test/java/org/apache/cloudstack/api/command/NetworkElementApiExecutorTest.java b/api/src/test/java/org/apache/cloudstack/api/command/NetworkElementApiExecutorTest.java new file mode 100644 index 000000000000..14314022f319 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/NetworkElementApiExecutorTest.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.exception.CloudRuntimeException; + +public class NetworkElementApiExecutorTest { + + @Test + public void executeMapsInvalidParameterValueExceptionToParamError() { + ServerApiException exception = assertThrows(ServerApiException.class, () -> + NetworkElementApiExecutor.execute(() -> { + throw new InvalidParameterValueException("bad parameter"); + })); + + assertEquals(ApiErrorCode.PARAM_ERROR, exception.getErrorCode()); + assertEquals("bad parameter", exception.getMessage()); + } + + @Test + public void executeMapsCloudRuntimeExceptionToInternalError() { + ServerApiException exception = assertThrows(ServerApiException.class, () -> + NetworkElementApiExecutor.execute(() -> { + throw new CloudRuntimeException("runtime failure"); + })); + + assertEquals(ApiErrorCode.INTERNAL_ERROR, exception.getErrorCode()); + assertEquals("runtime failure", exception.getMessage()); + } + + @Test + public void requireNonNullMapsNullResultToInternalErrorWithFailureMessage() { + ServerApiException exception = assertThrows(ServerApiException.class, () -> + NetworkElementApiExecutor.execute(() -> + NetworkElementApiExecutor.requireNonNull(null, "missing response"))); + + assertEquals(ApiErrorCode.INTERNAL_ERROR, exception.getErrorCode()); + assertEquals("missing response", exception.getMessage()); + } + + @Test + public void requireSuccessMapsFalseResultToInternalErrorWithFailureMessage() { + ServerApiException exception = assertThrows(ServerApiException.class, () -> + NetworkElementApiExecutor.execute(() -> + NetworkElementApiExecutor.requireSuccess(false, "operation failed"))); + + assertEquals(ApiErrorCode.INTERNAL_ERROR, exception.getErrorCode()); + assertEquals("operation failed", exception.getMessage()); + } +} diff --git a/client/pom.xml b/client/pom.xml index 55123de0f98e..df5969f09f33 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -29,8 +29,8 @@ - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api org.eclipse.jetty @@ -126,21 +126,6 @@ cloud-plugin-storage-volume-ontap ${project.version} - - org.apache.cloudstack - cloud-plugin-storage-volume-solidfire - ${project.version} - - - org.apache.cloudstack - cloud-plugin-storage-volume-cloudbyte - ${project.version} - - - org.apache.cloudstack - cloud-plugin-storage-volume-datera - ${project.version} - org.apache.cloudstack cloud-plugin-storage-volume-scaleio @@ -213,7 +198,7 @@ org.apache.cloudstack - cloud-plugin-user-authenticator-md5 + cloud-plugin-user-authenticator-bff-trusted-source ${project.version} @@ -226,11 +211,6 @@ cloud-plugin-user-authenticator-pbkdf2 ${project.version} - - org.apache.cloudstack - cloud-plugin-user-authenticator-plaintext - ${project.version} - org.apache.cloudstack cloud-plugin-user-authenticator-saml2 @@ -256,11 +236,6 @@ cloud-plugin-metrics ${project.version} - - org.apache.cloudstack - cloud-plugin-network-nvp - ${project.version} - org.apache.cloudstack cloud-plugin-network-palo-alto @@ -281,16 +256,6 @@ cloud-plugin-network-elb ${project.version} - - org.apache.cloudstack - cloud-plugin-network-bigswitch - ${project.version} - - - org.apache.cloudstack - cloud-plugin-network-ssp - ${project.version} - org.apache.cloudstack cloud-plugin-network-internallb @@ -306,11 +271,6 @@ cloud-plugin-network-opendaylight ${project.version} - - org.apache.cloudstack - cloud-plugin-network-vcs - ${project.version} - org.apache.cloudstack cloud-plugin-hypervisor-xenserver @@ -321,11 +281,6 @@ cloud-plugin-hypervisor-baremetal ${project.version} - - org.apache.cloudstack - cloud-plugin-hypervisor-ucs - ${project.version} - org.apache.cloudstack cloud-plugin-hypervisor-kvm @@ -562,16 +517,6 @@ cloud-plugin-non-strict-host-affinity ${project.version} - - org.apache.cloudstack - cloud-plugin-api-solidfire-intg-test - ${project.version} - - - org.apache.cloudstack - cloud-plugin-network-globodns - ${project.version} - org.apache.cloudstack cloud-plugin-cluster-drs-balanced @@ -716,17 +661,17 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on ${cs.bcprov.version} org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on ${cs.bcprov.version} org.bouncycastle - bctls-jdk15on + bctls-jdk18on ${cs.bcprov.version} @@ -868,11 +813,6 @@ match="classpath:componentContext.xml" replace="classpath:nonossComponentContext.xml" byline="true" /> - - - - - @@ -906,13 +846,13 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on false ${project.build.directory}/lib org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on false ${project.build.directory}/lib @@ -936,7 +876,7 @@ org.bouncycastle - bctls-jdk15on + bctls-jdk18on false ${project.build.directory}/lib @@ -966,14 +906,14 @@ junit:junit com.tngtech.java:junit-dataprovider org.mockito:mockito-all - org.hamcrest:hamcrest-all + org.hamcrest:hamcrest org.springframework:spring-test org.apache.tomcat.embed:tomcat-embed-core org.apache.geronimo.specs:geronimo-servlet_3.0_spec org.apache.geronimo.specs:geronimo-javamail_1.4_spec - org.bouncycastle:bcprov-jdk15on - org.bouncycastle:bcpkix-jdk15on - org.bouncycastle:bctls-jdk15on + org.bouncycastle:bcprov-jdk18on + org.bouncycastle:bcpkix-jdk18on + org.bouncycastle:bctls-jdk18on com.mysql:mysql-connector-j org.apache.cloudstack:cloud-plugin-storage-volume-storpool org.apache.cloudstack:cloud-plugin-storage-volume-linstor @@ -1093,11 +1033,6 @@ cloud-vmware-base ${project.version} - - org.apache.cloudstack - cloud-plugin-network-cisco-vnmc - ${project.version} - org.apache.cloudstack cloud-plugin-network-nsx @@ -1118,11 +1053,6 @@ cloud-plugin-api-vmware-sioc ${project.version} - - org.apache.cloudstack - cloud-plugin-network-contrail - ${project.version} - org.apache.cloudstack cloud-plugin-backup-veeam diff --git a/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java b/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java index 249451120f1c..e6ab45c8972b 100644 --- a/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java +++ b/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java @@ -20,11 +20,14 @@ import com.cloud.api.ApiServlet; import com.cloud.utils.StringUtils; -import org.eclipse.jetty.server.NCSARequestLog; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.RequestLog; +import org.eclipse.jetty.server.RequestLogWriter; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.util.DateCache; -import org.eclipse.jetty.util.component.LifeCycle; +import org.eclipse.jetty.util.component.AbstractLifeCycle; import java.net.InetAddress; import java.util.Locale; @@ -32,18 +35,30 @@ import static org.apache.commons.configuration.DataConfiguration.DEFAULT_DATE_FORMAT; -public class ACSRequestLog extends NCSARequestLog { +public class ACSRequestLog extends AbstractLifeCycle implements RequestLog { + private static final Logger LOG = LogManager.getLogger(ACSRequestLog.class); + private static final ThreadLocal buffers = ThreadLocal.withInitial(() -> new StringBuilder(256)); private final DateCache dateCache; + private final RequestLogWriter writer; public ACSRequestLog() { - super(); - TimeZone timeZone = TimeZone.getTimeZone("GMT"); Locale locale = Locale.getDefault(); dateCache = new DateCache(DEFAULT_DATE_FORMAT, locale, timeZone); + writer = new RequestLogWriter(); + writer.setTimeZone("GMT"); + writer.setAppend(true); + } + + public void setFilename(String filename) { + writer.setFilename(filename); + } + + public void setAppend(boolean append) { + writer.setAppend(append); } @Override @@ -66,20 +81,27 @@ public void log(Request request, Response response) { .append("\" ") .append(response.getStatus()) .append(" ") - .append(response.getHttpChannel().getBytesWritten()) // apply filter here? + .append(response.getHttpChannel().getBytesWritten()) .append(" \"-\" \"") .append(request.getHeader("User-Agent")) .append("\""); - write(sb.toString()); + writer.write(sb.toString()); } catch (Exception e) { LOG.warn("Unable to log request", e); } } @Override - protected void stop(LifeCycle lifeCycle) throws Exception { + protected void doStart() throws Exception { + writer.start(); + super.doStart(); + } + + @Override + protected void doStop() throws Exception { + writer.stop(); buffers.remove(); - super.stop(lifeCycle); + super.doStop(); } } diff --git a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java index 06477fff8986..7b5b4eda89f7 100644 --- a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java +++ b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java @@ -235,7 +235,7 @@ private void createHttpsConnector(final HttpConfiguration httpConfig) { // Configure SSL if (httpsEnable && StringUtils.isNotEmpty(keystoreFile) && new File(keystoreFile).exists()) { // SSL Context - final SslContextFactory sslContextFactory = new SslContextFactory.Server(); + final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); // Define keystore path and passwords sslContextFactory.setKeyStorePath(keystoreFile); @@ -275,7 +275,6 @@ private Pair createHandlers() { final GzipHandler gzipHandler = new GzipHandler(); gzipHandler.addIncludedMimeTypes("text/html", "text/xml", "text/css", "text/plain", "text/javascript", "application/javascript", "application/json", "application/xml"); gzipHandler.setIncludedMethods("GET", "POST"); - gzipHandler.setCompressionLevel(9); gzipHandler.setHandler(webApp); if (StringUtils.isEmpty(webAppLocation)) { @@ -307,8 +306,6 @@ private RequestLog createRequestLog() { } log.setFilename(logPath.getPath()); log.setAppend(true); - log.setLogTimeZone("GMT"); - log.setLogLatency(true); return log; } diff --git a/client/src/main/webapp/WEB-INF/web.xml b/client/src/main/webapp/WEB-INF/web.xml index 43bee7e59d88..c21c3fd2b2b4 100644 --- a/client/src/main/webapp/WEB-INF/web.xml +++ b/client/src/main/webapp/WEB-INF/web.xml @@ -31,6 +31,26 @@ com.cloud.api.ApiSessionListener + + + + httpMetricsFilter + com.cloud.observability.HttpMetricsFilter + + + httpMetricsFilter + /* + + + + tracingFilter + com.cloud.observability.TracingFilter + + + tracingFilter + /* + contextConfigLocation classpath:META-INF/cloudstack/webApplicationContext.xml @@ -54,6 +74,18 @@ 6 + + healthServlet + com.cloud.servlet.HealthServlet + 7 + + + + metricsServlet + com.cloud.servlet.MetricsServlet + 8 + + apiServlet /api/* @@ -64,6 +96,16 @@ /console + + healthServlet + /health/* + + + + metricsServlet + /metrics + + java.lang.Exception /error.html diff --git a/cloud-cli/bindir/cloud-tool b/cloud-cli/bindir/cloud-tool deleted file mode 100755 index 0f0815307a13..000000000000 --- a/cloud-cli/bindir/cloud-tool +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 - -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -import sys -import os - -sys.path.append(os.path.dirname(os.path.dirname(__file__))) - -import cloudtool - -ret = cloudtool.main() -if ret: - sys.exit(ret) diff --git a/cloud-cli/cloudapis/__init__.py b/cloud-cli/cloudapis/__init__.py deleted file mode 100644 index dd84d7b5c9bb..000000000000 --- a/cloud-cli/cloudapis/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - - -''' -Created on Aug 2, 2010 - -''' - -import os,pkgutil - -def get_all_apis(): - apis = [] - for x in pkgutil.walk_packages([os.path.dirname(__file__)]): - loader = x[0].find_module(x[1]) - try: module = loader.load_module("cloudapis." + x[1]) - except ImportError: continue - apis.append(module) - return apis - -def lookup_api(api_name): - api = None - matchingapi = [ x for x in get_all_apis() if api_name.replace("-","_") == x.__name__.split(".")[-1] ] - if not matchingapi: api = None - else: api = matchingapi[0] - if api: api = getattr(api,"implementor") - return api diff --git a/cloud-cli/cloudapis/cloud.py b/cloud-cli/cloudapis/cloud.py deleted file mode 100644 index 60175b9401d6..000000000000 --- a/cloud-cli/cloudapis/cloud.py +++ /dev/null @@ -1,196 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - - -'''Implements the CloudStack API''' - - -from cloudtool.utils import describe -import urllib.request, urllib.parse, urllib.error -import urllib.request, urllib.error, urllib.parse -import os -import xml.dom.minidom -import re -import base64 -import hmac -import hashlib -import http.client - -class CloudAPI: - - @describe("server", "Management Server host name or address") - @describe("apikey", "Management Server apiKey") - @describe("securitykey", "Management Server securityKey") - @describe("responseformat", "Response format: xml or json") - @describe("stripxml", "True if xml tags have to be stripped in the output, false otherwise") - def __init__(self, - server="127.0.0.1:8096", - responseformat="xml", - stripxml="true", - apiKey=None, - securityKey=None - ): - self.__dict__.update(locals()) - - def _make_request_with_keys(self,command,requests={}): - requests["command"] = command - requests["apiKey"] = self.apiKey - requests["response"] = "xml" - requests = list(zip(list(requests.keys()), list(requests.values()))) - requests.sort(key=lambda x: str.lower(x[0])) - - requestUrl = "&".join(["=".join([request[0], urllib.parse.quote_plus(str(request[1]))]) for request in requests]) - hashStr = "&".join(["=".join([str.lower(request[0]), urllib.parse.quote_plus(str.lower(str(request[1])))]) for request in requests]) - - sig = urllib.parse.quote_plus(base64.encodestring(hmac.new(self.securityKey, hashStr, hashlib.sha1).digest()).strip()) - - requestUrl += "&signature=%s"%sig - return requestUrl - - - def _make_request_with_auth(self, command, requests): - self.connection = http.client.HTTPConnection("%s"%(self.server)) - requests["command"] = command - requests["apiKey"] = self.apiKey - requests["response"] = self.responseformat - requests = list(zip(list(requests.keys()), list(requests.values()))) - requests.sort(key=lambda x: str.lower(x[0])) - - requestUrl = "&".join(["=".join([request[0], urllib.parse.quote(str(request[1],""))]) for request in requests]) - hashStr = "&".join(["=".join([str.lower(request[0]), urllib.parse.quote(str.lower(str(request[1])),"")]) for request in requests]) - - sig = urllib.parse.quote_plus(base64.encodestring(hmac.new(self.securityKey, str.lower(hashStr), hashlib.sha1).digest()).strip()) - - requestUrl += "&signature=%s"%sig - - self.connection.request("GET", "/client/api?%s"%requestUrl) - return self.connection.getresponse().read() - - def _make_request(self,command,parameters=None): - - '''Command is a string, parameters is a dictionary''' - if ":" in self.server: - host,port = self.server.split(":") - port = int(port) - else: - host = self.server - port = 8096 - - url = "http://" + self.server + "/client/api?" - - if not parameters: parameters = {} - if self.apiKey is not None and self.securityKey is not None: - return self._make_request_with_auth(command, parameters) - else: - parameters["command"] = command - parameters["response"] = self.responseformat - querystring = urllib.parse.urlencode(parameters) - - url += querystring - - f = urllib.request.urlopen(url) - data = f.read() - if self.stripxml == "true": - data=re.sub("<\?.*\?>", "\n", data); - data=re.sub("", "\n", data); - data=data.replace(">", "="); - data=data.replace("=<", "\n"); - data=data.replace("\n<", "\n"); - data=re.sub("\n.*cloud-stack-version=.*", "", data); - data=data.replace("\n\n\n", "\n"); - - return data - - -def load_dynamic_methods(): - '''creates smart function objects for every method in the commands.xml file''' - - def getText(nodelist): - rc = [] - for node in nodelist: - if node.nodeType == node.TEXT_NODE: rc.append(node.data) - return ''.join(rc) - - # FIXME figure out installation and packaging - xmlfile = os.path.join("/etc/cloud/cli/","commands.xml") - dom = xml.dom.minidom.parse(xmlfile) - - for cmd in dom.getElementsByTagName("command"): - name = getText(cmd.getElementsByTagName('name')[0].childNodes).strip() - assert name - - description = getText(cmd.getElementsByTagName('description')[0].childNodes).strip() - if description: - description = '"""%s"""' % description - else: description = '' - arguments = [] - options = [] - descriptions = [] - - for param in cmd.getElementsByTagName("request")[0].getElementsByTagName("arg"): - argname = getText(param.getElementsByTagName('name')[0].childNodes).strip() - assert argname - - required = getText(param.getElementsByTagName('required')[0].childNodes).strip() - if required == 'true': required = True - elif required == 'false': required = False - else: raise AssertionError("Not reached") - if required: arguments.append(argname) - options.append(argname) - - #import ipdb; ipdb.set_trace() - requestDescription = param.getElementsByTagName('description') - if requestDescription: - descriptionParam = getText(requestDescription[0].childNodes) - else: - descriptionParam = '' - if descriptionParam: descriptions.append( (argname,descriptionParam) ) - - funcparams = ["self"] + [ "%s=None"%o for o in options ] - funcparams = ", ".join(funcparams) - - code = """ - def %s(%s): - %s - parms = dict(locals()) - del parms["self"] - for arg in %r: - if locals()[arg] is None: - raise TypeError, "%%s is a required option"%%arg - for k,v in parms.items(): - if v is None: del parms[k] - output = self._make_request("%s",parms) - return output - """%(name,funcparams,description,arguments,name) - - namespace = {} - exec(code.strip(), namespace) - - func = namespace[name] - for argname,description in descriptions: - func = describe(argname,description)(func) - - yield (name,func) - - -for name,meth in load_dynamic_methods(): - setattr(CloudAPI, name, meth) - -implementor = CloudAPI - -del name,meth,describe,load_dynamic_methods diff --git a/cloud-cli/cloudtool/__init__.py b/cloud-cli/cloudtool/__init__.py deleted file mode 100644 index c5785dcbd827..000000000000 --- a/cloud-cli/cloudtool/__init__.py +++ /dev/null @@ -1,71 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - - -''' -Created on Aug 2, 2010 - -''' - -import sys -import cloudapis as apis -import cloudtool.utils as utils - - -def main(argv=None): - - #import ipdb; ipdb.set_trace() - if argv == None: - argv = sys.argv - - prelim_args = [ x for x in argv[0:] if not x.startswith('-') ] - parser = utils.get_parser() - - api = __import__("cloudapis") - apis = getattr(api, "implementor") - if len(prelim_args) == 1: - commandlist = utils.get_command_list(apis) - parser.error("you need to specify a command name as the first argument\n\nCommands supported by the %s API:\n"%prelim_args[0] + "\n".join(commandlist)) - - command = utils.lookup_command_in_api(apis,prelim_args[1]) - if not command: parser.error("command %r not supported by the %s API"%(prelim_args[1],prelim_args[0])) - - argv = argv[1:] - if len(argv) == 1: - argv.append("--help") - - parser = utils.get_parser(apis.__init__,command) - opts,args,api_optionsdict,cmd_optionsdict = parser.parse_args(argv) - - - try: - api = apis(**api_optionsdict) - except utils.OptParseError as e: - parser.error(str(e)) - - command = utils.lookup_command_in_api(api,args[0]) - - # we now discard the first two arguments as those necessarily are the api and command names - args = args[2:] - - try: return command(*args,**cmd_optionsdict) - except TypeError as e: parser.error(str(e)) - - -if __name__ == '__main__': - main(argv) diff --git a/cloud-cli/cloudtool/utils.py b/cloud-cli/cloudtool/utils.py deleted file mode 100644 index 0fc21e19924e..000000000000 --- a/cloud-cli/cloudtool/utils.py +++ /dev/null @@ -1,169 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - - - -''' -Created on Aug 2, 2010 - -''' - - -import sys -import os -import inspect -from optparse import OptionParser, OptParseError, BadOptionError, OptionError, OptionConflictError, OptionValueError -import cloudapis as apis - - -def describe(name,desc): - def inner(decoratee): - if not hasattr(decoratee,"descriptions"): decoratee.descriptions = {} - decoratee.descriptions[name] = desc - return decoratee - return inner - - -def error(msg): - sys.stderr.write(msg) - sys.stderr.write("\n") - - -class MyOptionParser(OptionParser): - def error(self, msg): - error("%s: %s\n" % (self.get_prog_name(),msg)) - self.print_usage(sys.stderr) - self.exit(os.EX_USAGE) - - def parse_args(self,*args,**kwargs): - options,arguments = OptionParser.parse_args(self,*args,**kwargs) - - def prune_options(options,alist): - """Given 'options' -- a list of arguments to OptionParser.add_option, - and a set of optparse Values, return a dictionary of only those values - that apply exclusively to 'options'""" - return dict( [ (k,getattr(options,k)) for k in dir(options) if k in alist ] ) - - api_options = prune_options(options,self.api_dests) - cmd_options = prune_options(options,self.cmd_dests) - - return options,arguments,api_options,cmd_options - - -def get_parser(api_callable=None,cmd_callable=None): # this should probably be the __init__ method of myoptionparser - - def getdefaulttag(default): - if default is not None: return " [Default: %default]" - return '' - - def get_arguments_and_options(callable): - """Infers and returns arguments and options based on a callable's signature. - Cooperates with decorator @describe""" - try: - funcargs = inspect.getargspec(callable).args - defaults = inspect.getargspec(callable).defaults - except: - funcargs = inspect.getargspec(callable)[0] - defaults = inspect.getargspec(callable)[3] - if not defaults: defaults = [] - args = funcargs[1:len(funcargs)-len(defaults)] # this assumes self, so assumes methods - opts = funcargs[len(funcargs)-len(defaults):] - try: descriptions = callable.descriptions - except AttributeError: descriptions = {} - arguments = [ (argname, descriptions.get(argname,'') ) for argname in args ] - options = [ [ - ("--%s"%argname.replace("_","-"),), - { - "dest":argname, - "help":descriptions.get(argname,'') + getdefaulttag(default), - "default":default, - } - ] for argname,default in zip(opts,defaults) ] - return arguments,options - - basic_usage = "usage: %prog [options...] " - - api_name = "" - cmd_name = "" - description = "%prog is a command-line tool to access several cloud APIs." - arguments = '' - argexp = "" - - if api_callable: - api_name = api_callable.__module__.split(".")[-1].replace("_","-") - api_arguments,api_options = get_arguments_and_options(api_callable) - assert len(api_arguments) is 0 # no mandatory arguments for class initializers - - if cmd_callable: - cmd_name = cmd_callable.__name__.replace("_","-") - cmd_arguments,cmd_options = get_arguments_and_options(cmd_callable) - if cmd_arguments: - arguments = " " + " ".join( [ s[0].upper() for s in cmd_arguments ] ) - argexp = "\n\nArguments:\n" + "\n".join ( " %s\n %s"%(s.upper(),u) for s,u in cmd_arguments ) - description = cmd_callable.__doc__ - - api_command = "%s %s"%(api_name,cmd_name) - - if description: description = "\n\n" + description - else: description = '' - - usage = basic_usage + api_command + arguments + description + argexp - - parser = MyOptionParser(usage=usage, add_help_option=False) - - parser.add_option('--help', action="help") - - group = parser.add_option_group("General options") - group.add_option('-v', '--verbose', dest="verbose", help="Print extra output") - - parser.api_dests = [] - if api_callable and api_options: - group = parser.add_option_group("Options for the %s API"%api_name) - for a in api_options: - group.add_option(a[0][0],**a[1]) - parser.api_dests.append(a[1]["dest"]) - - parser.cmd_dests = [] - if cmd_callable and cmd_options: - group = parser.add_option_group("Options for the %s command"%cmd_name) - for a in cmd_options: - group.add_option(a[0][0],**a[1]) - parser.cmd_dests.append(a[1]["dest"]) - - return parser - -def lookup_command_in_api(api,command_name): - command = getattr(api,command_name.replace("-","_"),None) - return command - -def get_api_list(api): - apilist = [] - for cmd_name in dir(api): - cmd = getattr(api,cmd_name) - if callable(cmd) and not cmd_name.startswith("_"): - apilist.append(cmd_name) - return apilist - -def get_command_list(api): - cmds = [] - for cmd_name in dir(api): - cmd = getattr(api,cmd_name) - if callable(cmd) and not cmd_name.startswith("_"): - if cmd.__doc__:docstring = cmd.__doc__ - else:docstring = '' - cmds.append( " %s" % (cmd_name.replace('_','-')) ) - return cmds diff --git a/core/src/main/java/com/cloud/resource/RequestWrapper.java b/core/src/main/java/com/cloud/resource/RequestWrapper.java index 54d8b289c8d6..1e122826aadf 100644 --- a/core/src/main/java/com/cloud/resource/RequestWrapper.java +++ b/core/src/main/java/com/cloud/resource/RequestWrapper.java @@ -140,10 +140,8 @@ protected Hashtable, CommandWrapper> processAnnotations continue; } try { - commands.put(annotation.handles(), wrapper.newInstance()); - } catch (final InstantiationException e) { - logger.warn(MessageFormat.format(errorMessage, e.getLocalizedMessage(), wrapper.toString())); - } catch (final IllegalAccessException e) { + commands.put(annotation.handles(), wrapper.getDeclaredConstructor().newInstance()); + } catch (final ReflectiveOperationException e) { logger.warn(MessageFormat.format(errorMessage, e.getLocalizedMessage(), wrapper.toString())); } } diff --git a/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferAnswer.java new file mode 100644 index 000000000000..50f720f1ab7d --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferAnswer.java @@ -0,0 +1,57 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class CreateImageTransferAnswer extends Answer { + private String imageTransferId; + private String transferUrl; + + public CreateImageTransferAnswer() { + } + + public CreateImageTransferAnswer(CreateImageTransferCommand command, boolean success, String details) { + super(command, success, details); + } + + public CreateImageTransferAnswer(CreateImageTransferCommand command, boolean success, String details, + String imageTransferId, String transferUrl) { + super(command, success, details); + this.imageTransferId = imageTransferId; + this.transferUrl = transferUrl; + } + + public String getImageTransferId() { + return imageTransferId; + } + + public void setImageTransferId(String imageTransferId) { + this.imageTransferId = imageTransferId; + } + + public String getTransferUrl() { + return transferUrl; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java new file mode 100644 index 000000000000..99b6dc8643c0 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java @@ -0,0 +1,140 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class CreateImageTransferCommand extends Command { + public enum Direction { + upload, + download; + + public boolean matches(String value) { + return name().equalsIgnoreCase(value); + } + } + + public enum Backend { + nbd, + file + } + + private String transferId; + private String exportName; + private String socket; + private String direction; + private String checkpointId; + private String file; + private Backend backend; + private int idleTimeoutSeconds; + @LogLevel(LogLevel.Log4jLevel.Off) + private String token; + + public CreateImageTransferCommand() { + } + + private CreateImageTransferCommand(String transferId, String direction, String socket, int idleTimeoutSeconds) { + this(transferId, direction, socket, idleTimeoutSeconds, null); + } + + private CreateImageTransferCommand(String transferId, String direction, String socket, int idleTimeoutSeconds, String token) { + this.transferId = transferId; + this.direction = direction; + this.socket = socket; + this.idleTimeoutSeconds = idleTimeoutSeconds; + this.token = token; + } + + public CreateImageTransferCommand(String transferId, String direction, String exportName, String socket, + String checkpointId, int idleTimeoutSeconds) { + this(transferId, direction, socket, idleTimeoutSeconds); + this.backend = Backend.nbd; + this.exportName = exportName; + this.checkpointId = checkpointId; + } + + public CreateImageTransferCommand(String transferId, String direction, String exportName, String socket, + String checkpointId, int idleTimeoutSeconds, String token) { + this(transferId, direction, socket, idleTimeoutSeconds, token); + this.backend = Backend.nbd; + this.exportName = exportName; + this.checkpointId = checkpointId; + } + + public CreateImageTransferCommand(String transferId, String direction, String socket, String file, int idleTimeoutSeconds) { + this(transferId, direction, socket, idleTimeoutSeconds); + if (Direction.download.matches(direction)) { + throw new IllegalArgumentException("File backend is only supported for upload"); + } + this.backend = Backend.file; + this.file = file; + } + + public CreateImageTransferCommand(String transferId, String direction, String socket, String file, int idleTimeoutSeconds, String token) { + this(transferId, direction, socket, idleTimeoutSeconds, token); + if (Direction.download.matches(direction)) { + throw new IllegalArgumentException("File backend is only supported for upload"); + } + this.backend = Backend.file; + this.file = file; + } + + public String getTransferId() { + return transferId; + } + + public String getExportName() { + return exportName; + } + + public String getSocket() { + return socket; + } + + public String getDirection() { + return direction; + } + + public String getCheckpointId() { + return checkpointId; + } + + public String getFile() { + return file; + } + + public Backend getBackend() { + return backend; + } + + public int getIdleTimeoutSeconds() { + return idleTimeoutSeconds; + } + + public String getToken() { + return token; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/DeleteVmCheckpointCommand.java b/core/src/main/java/org/apache/cloudstack/backup/DeleteVmCheckpointCommand.java new file mode 100644 index 000000000000..66789ef94626 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/DeleteVmCheckpointCommand.java @@ -0,0 +1,75 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import java.util.Map; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class DeleteVmCheckpointCommand extends Command { + private String vmName; + private String checkpointId; + private Map diskPathUuidMap; + @LogLevel(LogLevel.Log4jLevel.Off) + private Map diskPathPassphraseMap; + private boolean stoppedVM; + + public DeleteVmCheckpointCommand() { + } + + public DeleteVmCheckpointCommand(String vmName, String checkpointId, Map diskPathUuidMap, boolean stoppedVM) { + this(vmName, checkpointId, diskPathUuidMap, null, stoppedVM); + } + + public DeleteVmCheckpointCommand(String vmName, String checkpointId, Map diskPathUuidMap, + Map diskPathPassphraseMap, boolean stoppedVM) { + this.vmName = vmName; + this.checkpointId = checkpointId; + this.diskPathUuidMap = diskPathUuidMap; + this.diskPathPassphraseMap = diskPathPassphraseMap; + this.stoppedVM = stoppedVM; + } + + public String getVmName() { + return vmName; + } + + public String getCheckpointId() { + return checkpointId; + } + + public Map getDiskPathUuidMap() { + return diskPathUuidMap; + } + + public Map getDiskPathPassphraseMap() { + return diskPathPassphraseMap; + } + + public boolean isStoppedVM() { + return stoppedVM; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/FinalizeImageTransferCommand.java b/core/src/main/java/org/apache/cloudstack/backup/FinalizeImageTransferCommand.java new file mode 100644 index 000000000000..657f38d04036 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/FinalizeImageTransferCommand.java @@ -0,0 +1,42 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class FinalizeImageTransferCommand extends Command { + private String transferId; + + public FinalizeImageTransferCommand() { + } + + public FinalizeImageTransferCommand(String transferId) { + this.transferId = transferId; + } + + public String getTransferId() { + return transferId; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartBackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/StartBackupAnswer.java new file mode 100644 index 000000000000..ff669ce2c8d1 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartBackupAnswer.java @@ -0,0 +1,46 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class StartBackupAnswer extends Answer { + private Long checkpointCreateTime; + + public StartBackupAnswer() { + } + + public StartBackupAnswer(StartBackupCommand command, boolean success, String details) { + super(command, success, details); + } + + public StartBackupAnswer(StartBackupCommand command, boolean success, String details, Long checkpointCreateTime) { + super(command, success, details); + this.checkpointCreateTime = checkpointCreateTime; + } + + public Long getCheckpointCreateTime() { + return checkpointCreateTime; + } + + public void setCheckpointCreateTime(Long checkpointCreateTime) { + this.checkpointCreateTime = checkpointCreateTime; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java new file mode 100644 index 000000000000..398a7fdaa06f --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java @@ -0,0 +1,93 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import java.util.Map; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class StartBackupCommand extends Command { + private String vmName; + private String toCheckpointId; + private String fromCheckpointId; + private Long fromCheckpointCreateTime; + private String socket; + private Map diskPathUuidMap; + private boolean stoppedVM; + @LogLevel(LogLevel.Log4jLevel.Off) + private Map diskPathPassphraseMap; + + public StartBackupCommand() { + } + + public StartBackupCommand(String vmName, String toCheckpointId, String fromCheckpointId, Long fromCheckpointCreateTime, + String socket, Map diskPathUuidMap, Map diskPathPassphraseMap, boolean stoppedVM) { + this.vmName = vmName; + this.toCheckpointId = toCheckpointId; + this.fromCheckpointId = fromCheckpointId; + this.fromCheckpointCreateTime = fromCheckpointCreateTime; + this.socket = socket; + this.diskPathUuidMap = diskPathUuidMap; + this.diskPathPassphraseMap = diskPathPassphraseMap; + this.stoppedVM = stoppedVM; + } + + public String getVmName() { + return vmName; + } + + public String getToCheckpointId() { + return toCheckpointId; + } + + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public Long getFromCheckpointCreateTime() { + return fromCheckpointCreateTime; + } + + public String getSocket() { + return socket; + } + + public Map getDiskPathUuidMap() { + return diskPathUuidMap; + } + + public boolean isIncremental() { + return fromCheckpointId != null && !fromCheckpointId.isEmpty(); + } + + public boolean isStoppedVM() { + return stoppedVM; + } + + public Map getDiskPathPassphraseMap() { + return diskPathPassphraseMap; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerAnswer.java new file mode 100644 index 000000000000..ddc22a1e0823 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerAnswer.java @@ -0,0 +1,57 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class StartNBDServerAnswer extends Answer { + private String imageTransferId; + private String transferUrl; + + public StartNBDServerAnswer() { + } + + public StartNBDServerAnswer(StartNBDServerCommand command, boolean success, String details) { + super(command, success, details); + } + + public StartNBDServerAnswer(StartNBDServerCommand command, boolean success, String details, + String imageTransferId, String transferUrl) { + super(command, success, details); + this.imageTransferId = imageTransferId; + this.transferUrl = transferUrl; + } + + public String getImageTransferId() { + return imageTransferId; + } + + public void setImageTransferId(String imageTransferId) { + this.imageTransferId = imageTransferId; + } + + public String getTransferUrl() { + return transferUrl; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerCommand.java new file mode 100644 index 000000000000..2e0a3d5adef6 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerCommand.java @@ -0,0 +1,90 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import java.util.Arrays; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class StartNBDServerCommand extends Command { + private String transferId; + private String exportName; + private String volumePath; + private String socket; + private String direction; + private String fromCheckpointId; + @LogLevel(LogLevel.Log4jLevel.Off) + private byte[] passphrase; + + public StartNBDServerCommand() { + } + + public StartNBDServerCommand(String transferId, String exportName, String volumePath, String socket, + String direction, String fromCheckpointId, byte[] passphrase) { + this.transferId = transferId; + this.exportName = exportName; + this.volumePath = volumePath; + this.socket = socket; + this.direction = direction; + this.fromCheckpointId = fromCheckpointId; + this.passphrase = passphrase; + } + + public String getTransferId() { + return transferId; + } + + public String getExportName() { + return exportName; + } + + public String getVolumePath() { + return volumePath; + } + + public String getSocket() { + return socket; + } + + public String getDirection() { + return direction; + } + + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public byte[] getPassphrase() { + return passphrase; + } + + public void clearPassphrase() { + if (passphrase != null) { + Arrays.fill(passphrase, (byte) 0); + passphrase = null; + } + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StopBackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/StopBackupAnswer.java new file mode 100644 index 000000000000..3bcd6adc8c18 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StopBackupAnswer.java @@ -0,0 +1,32 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class StopBackupAnswer extends Answer { + + public StopBackupAnswer() { + } + + public StopBackupAnswer(StopBackupCommand command, boolean success, String details) { + super(command, success, details); + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StopBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StopBackupCommand.java new file mode 100644 index 000000000000..91caaa8e1cd1 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StopBackupCommand.java @@ -0,0 +1,54 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class StopBackupCommand extends Command { + private String vmName; + private Long vmId; + private Long backupId; + + public StopBackupCommand() { + } + + public StopBackupCommand(String vmName, Long vmId, Long backupId) { + this.vmName = vmName; + this.vmId = vmId; + this.backupId = backupId; + } + + public String getVmName() { + return vmName; + } + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StopNBDServerCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StopNBDServerCommand.java new file mode 100644 index 000000000000..aaada8b7bf7f --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StopNBDServerCommand.java @@ -0,0 +1,48 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class StopNBDServerCommand extends Command { + private String transferId; + private String direction; + + public StopNBDServerCommand() { + } + + public StopNBDServerCommand(String transferId, String direction) { + this.transferId = transferId; + this.direction = direction; + } + + public String getTransferId() { + return transferId; + } + + public String getDirection() { + return direction; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index 01c568d78916..0d36d1c9fa97 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -75,7 +75,7 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> + value="SimpleInvestigator,XenServerInvestigator,KVMInvestigator,HypervInvestigator,VMwareInvestigator,PingInvestigator,ManagementIPSysVMInvestigator" /> diff --git a/debian/control b/debian/control index 2b8ce929c639..fd178cc80083 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: cloudstack Section: libs Priority: extra Maintainer: The Apache CloudStack Team -Build-Depends: debhelper (>= 9), openjdk-17-jdk | java17-sdk | java17-jdk | zulu-17 | openjdk-11-jdk | java11-sdk | java11-jdk | zulu-11, genisoimage, +Build-Depends: debhelper (>= 9), openjdk-21-jdk | java21-sdk | java21-jdk | zulu-21 | openjdk-17-jdk | java17-sdk | java17-jdk | zulu-17, genisoimage, python-mysql.connector | python3-mysql.connector | mysql-connector-python-py3, maven (>= 3) | maven3, python3 (>= 3), python3-setuptools, nodejs (>= 12), lsb-release, dh-systemd | debhelper (>= 13) @@ -17,14 +17,14 @@ Description: A common package which contains files which are shared by several C Package: cloudstack-management Architecture: all -Depends: ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), net-tools, sudo, python3-mysql.connector | mysql-connector-python-py3, augeas-tools, mysql-client | mariadb-client, adduser, bzip2, ipmitool, file, gawk, iproute2, qemu-utils, rng-tools, python3-dnspython, lsb-release, init-system-helpers (>= 1.14~), python3-setuptools +Depends: ${python3:Depends}, openjdk-21-jre-headless | java21-runtime-headless | java21-runtime | zulu-21 | openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), net-tools, sudo, python3-mysql.connector | mysql-connector-python-py3, augeas-tools, mysql-client | mariadb-client, adduser, bzip2, ipmitool, file, gawk, iproute2, qemu-utils, rng-tools, python3-dnspython, lsb-release, init-system-helpers (>= 1.14~), python3-setuptools Conflicts: cloud-server, cloud-client, cloud-client-ui Description: CloudStack server library The CloudStack management server Package: cloudstack-agent Architecture: all -Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat +Depends: ${python:Depends}, ${python3:Depends}, openjdk-21-jre-headless | java21-runtime-headless | java21-runtime | zulu-21 | openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat, python3-libnbd, socat Recommends: init-system-helpers Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts Description: CloudStack agent @@ -34,7 +34,7 @@ Description: CloudStack agent Package: cloudstack-usage Architecture: all -Depends: openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), init-system-helpers +Depends: openjdk-21-jre-headless | java21-runtime-headless | java21-runtime | zulu-21 | openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), init-system-helpers Description: CloudStack usage monitor The CloudStack usage monitor provides usage accounting across the entire cloud for cloud operators to charge based on usage parameters. diff --git a/deploy/helm/cloudstack-management/Chart.yaml b/deploy/helm/cloudstack-management/Chart.yaml new file mode 100644 index 000000000000..9ba6b57118cc --- /dev/null +++ b/deploy/helm/cloudstack-management/Chart.yaml @@ -0,0 +1,16 @@ +apiVersion: v2 +name: cloudstack-management +description: Apache CloudStack management server (fork) — Kubernetes-ready with health probes, Prometheus scraping, OpenTelemetry tracing, and structured JSON logging. +type: application +version: 0.1.0 +appVersion: "4.23.0.0-SNAPSHOT" +keywords: + - cloudstack + - iaas + - cloud +home: https://github.com/d4m14ndx/cloudstack +sources: + - https://github.com/d4m14ndx/cloudstack +maintainers: + - name: CloudStack Fork + email: dev@example.com diff --git a/deploy/helm/cloudstack-management/templates/_helpers.tpl b/deploy/helm/cloudstack-management/templates/_helpers.tpl new file mode 100644 index 000000000000..ac6841d4d3a9 --- /dev/null +++ b/deploy/helm/cloudstack-management/templates/_helpers.tpl @@ -0,0 +1,47 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "cloudstack-management.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "cloudstack-management.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Chart label +*/}} +{{- define "cloudstack-management.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "cloudstack-management.labels" -}} +helm.sh/chart: {{ include "cloudstack-management.chart" . }} +{{ include "cloudstack-management.selectorLabels" . }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "cloudstack-management.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cloudstack-management.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/deploy/helm/cloudstack-management/templates/deployment.yaml b/deploy/helm/cloudstack-management/templates/deployment.yaml new file mode 100644 index 000000000000..e8383cd11d47 --- /dev/null +++ b/deploy/helm/cloudstack-management/templates/deployment.yaml @@ -0,0 +1,132 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "cloudstack-management.fullname" . }} + labels: + {{- include "cloudstack-management.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "cloudstack-management.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "cloudstack-management.selectorLabels" . | nindent 8 }} + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: cloudstack + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + - name: https + containerPort: 8443 + protocol: TCP + env: + - name: CLOUDSTACK_LOG_FORMAT + value: {{ .Values.logFormat | quote }} + - name: JAVA_OPTS + value: {{ .Values.javaOpts | quote }} + - name: DB_HOST + value: {{ .Values.database.host | quote }} + - name: DB_PORT + value: {{ .Values.database.port | quote }} + - name: DB_NAME + value: {{ .Values.database.name | quote }} + - name: DB_USER + value: {{ .Values.database.user | quote }} + - name: DB_PASSWORD + {{- if .Values.database.existingSecret }} + valueFrom: + secretKeyRef: + name: {{ .Values.database.existingSecret }} + key: {{ .Values.database.existingSecretKey }} + {{- else }} + value: {{ .Values.database.password | quote }} + {{- end }} + {{- if .Values.tracing.enabled }} + - name: OTEL_SERVICE_NAME + value: {{ .Values.tracing.serviceName | quote }} + - name: OTEL_TRACES_EXPORTER + value: {{ .Values.tracing.exporter | quote }} + - name: OTEL_EXPORTER_OTLP_ENDPOINT + value: {{ .Values.tracing.endpoint | quote }} + - name: OTEL_EXPORTER_OTLP_PROTOCOL + value: {{ .Values.tracing.protocol | quote }} + - name: OTEL_TRACES_SAMPLER + value: parentbased_traceidratio + - name: OTEL_TRACES_SAMPLER_ARG + value: {{ .Values.tracing.samplerRatio | quote }} + {{- end }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.livenessProbe }} + livenessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.readinessProbe }} + readinessProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.startupProbe }} + startupProbe: + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- if .Values.configOverride.enabled }} + volumeMounts: + {{- if .Values.configOverride.serverPropertiesConfigMap }} + - name: server-properties + mountPath: /etc/cloudstack/management/server.properties + subPath: server.properties + readOnly: true + {{- end }} + {{- if .Values.configOverride.dbPropertiesConfigMap }} + - name: db-properties + mountPath: /etc/cloudstack/management/db.properties + subPath: db.properties + readOnly: true + {{- end }} + {{- end }} + {{- if .Values.configOverride.enabled }} + volumes: + {{- if .Values.configOverride.serverPropertiesConfigMap }} + - name: server-properties + configMap: + name: {{ .Values.configOverride.serverPropertiesConfigMap }} + {{- end }} + {{- if .Values.configOverride.dbPropertiesConfigMap }} + - name: db-properties + configMap: + name: {{ .Values.configOverride.dbPropertiesConfigMap }} + {{- end }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/helm/cloudstack-management/templates/ingress.yaml b/deploy/helm/cloudstack-management/templates/ingress.yaml new file mode 100644 index 000000000000..11fe005ee787 --- /dev/null +++ b/deploy/helm/cloudstack-management/templates/ingress.yaml @@ -0,0 +1,35 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "cloudstack-management.fullname" . }} + labels: + {{- include "cloudstack-management.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- toYaml .Values.ingress.tls | nindent 4 }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "cloudstack-management.fullname" $ }} + port: + number: {{ $.Values.service.httpPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/deploy/helm/cloudstack-management/templates/service.yaml b/deploy/helm/cloudstack-management/templates/service.yaml new file mode 100644 index 000000000000..d3bf4ceb3e06 --- /dev/null +++ b/deploy/helm/cloudstack-management/templates/service.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "cloudstack-management.fullname" . }} + labels: + {{- include "cloudstack-management.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.httpPort }} + targetPort: http + protocol: TCP + name: http + - port: {{ .Values.service.httpsPort }} + targetPort: https + protocol: TCP + name: https + selector: + {{- include "cloudstack-management.selectorLabels" . | nindent 4 }} diff --git a/deploy/helm/cloudstack-management/templates/servicemonitor.yaml b/deploy/helm/cloudstack-management/templates/servicemonitor.yaml new file mode 100644 index 000000000000..8c73a54a6626 --- /dev/null +++ b/deploy/helm/cloudstack-management/templates/servicemonitor.yaml @@ -0,0 +1,24 @@ +{{- if .Values.serviceMonitor.enabled -}} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "cloudstack-management.fullname" . }} + namespace: {{ default .Release.Namespace .Values.serviceMonitor.namespace }} + labels: + {{- include "cloudstack-management.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + selector: + matchLabels: + {{- include "cloudstack-management.selectorLabels" . | nindent 6 }} + namespaceSelector: + matchNames: + - {{ .Release.Namespace }} + endpoints: + - port: http + path: /client/metrics + interval: {{ .Values.serviceMonitor.interval }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} +{{- end }} diff --git a/deploy/helm/cloudstack-management/values.yaml b/deploy/helm/cloudstack-management/values.yaml new file mode 100644 index 000000000000..c116006e1b6d --- /dev/null +++ b/deploy/helm/cloudstack-management/values.yaml @@ -0,0 +1,131 @@ +# Default values for cloudstack-management. + +replicaCount: 1 + +image: + repository: ghcr.io/d4m14ndx/cloudstack-management + pullPolicy: IfNotPresent + tag: "" # Overrides the image tag (defaults to the chart appVersion). + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +# External MySQL connection details (set these for any non-toy deployment). +database: + host: mysql.example.internal + port: 3306 + name: cloud + user: cloud + # If using an existing Secret for the password: + existingSecret: "" + existingSecretKey: password + # Or set a password literal (avoid in production): + password: "" + +# Logging — set to "json" for ECS-formatted structured logs (consumed by Loki / ELK / Datadog). +logFormat: text + +# JVM memory settings. +javaOpts: "-Xmx2g -Xms512m" + +# OpenTelemetry tracing — set endpoint + exporter to enable distributed traces. +tracing: + enabled: false + serviceName: cloudstack-management + exporter: otlp # set to "none" to disable + endpoint: "" # e.g. http://otel-collector.observability:4318 + protocol: http/protobuf + samplerRatio: "0.1" + +service: + type: ClusterIP + httpPort: 8080 + httpsPort: 8443 + +ingress: + enabled: false + className: "" + annotations: {} + # cert-manager.io/cluster-issuer: letsencrypt + # nginx.ingress.kubernetes.io/proxy-body-size: 16m + hosts: + - host: cloudstack.example.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: cloudstack-tls + # hosts: + # - cloudstack.example.com + +# Prometheus scraping. The Phase 4 /metrics endpoint emits JVM, process, +# and HTTP metrics. A PodMonitor/ServiceMonitor for Prometheus Operator +# is created when `serviceMonitor.enabled=true`. +serviceMonitor: + enabled: false + namespace: "" # leave empty to install in release namespace + labels: {} # extra labels for the ServiceMonitor (e.g. release: kube-prometheus-stack) + interval: 30s + scrapeTimeout: 10s + +# Pod-level Prometheus annotations (used by simpler scrape configs). +podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/path: /client/metrics + prometheus.io/port: "8080" + +podSecurityContext: + fsGroup: 1000 + +securityContext: + runAsUser: 1000 + runAsGroup: 1000 + runAsNonRoot: true + allowPrivilegeEscalation: false + capabilities: + drop: [ALL] + readOnlyRootFilesystem: false # CloudStack writes to /var/lib/cloudstack at runtime + +# Probe paths target the Phase 4 health endpoints. +livenessProbe: + httpGet: + path: /client/health/live + port: http + initialDelaySeconds: 120 + periodSeconds: 20 + failureThreshold: 5 +readinessProbe: + httpGet: + path: /client/health/ready + port: http + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 3 +startupProbe: + httpGet: + path: /client/health/live + port: http + failureThreshold: 60 + periodSeconds: 5 + +resources: + requests: + cpu: 500m + memory: 1Gi + limits: + memory: 4Gi + +nodeSelector: {} +tolerations: [] +affinity: {} + +extraEnv: [] + # - name: SOMETHING + # value: somevalue + +# Mount your own server.properties / db.properties from a ConfigMap. +configOverride: + enabled: false + serverPropertiesConfigMap: "" # ConfigMap key: server.properties + dbPropertiesConfigMap: "" # ConfigMap key: db.properties diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000000..b45b0c41d9bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,183 @@ +# Local development stack: management server + MySQL + Phase 5 web dependencies. +# +# Usage: +# docker compose up --build +# docker compose --profile auth up --build +# +# Then point a browser at http://localhost:8080/client +# Phase 5 web UI at http://localhost:3000 +# Authentik local dev UI at http://authentik.localhost:9000 when the auth profile is enabled +# Metrics at http://localhost:8080/client/metrics +# Health at http://localhost:8080/client/health/ready + +services: + mysql: + image: mysql:8.0 + environment: + MYSQL_ROOT_PASSWORD: cloud + MYSQL_DATABASE: cloud + MYSQL_USER: cloud + MYSQL_PASSWORD: cloud + command: + - --default-authentication-plugin=mysql_native_password + - --sql-mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION + volumes: + - mysql-data:/var/lib/mysql + ports: + - "3306:3306" + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-pcloud"] + interval: 10s + timeout: 5s + retries: 10 + start_period: 30s + + cloudstack: + build: + context: . + dockerfile: Dockerfile + depends_on: + mysql: + condition: service_healthy + environment: + # Exercises the JSON logging path added in Phase 4 (CLOUDSTACK_LOG_FORMAT) + CLOUDSTACK_LOG_FORMAT: text + # OpenTelemetry: uncomment to export traces + # OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4318 + # OTEL_TRACES_EXPORTER: otlp + JAVA_OPTS: "-Xmx2g -Xms512m" + ports: + - "8080:8080" + - "8443:8443" + # Mount your own server.properties/db.properties to override defaults + # volumes: + # - ./local-config/server.properties:/etc/cloudstack/management/server.properties:ro + # - ./local-config/db.properties:/etc/cloudstack/management/db.properties:ro + healthcheck: + test: ["CMD", "curl", "-fsS", "http://localhost:8080/client/health/ready"] + interval: 15s + timeout: 5s + retries: 6 + start_period: 180s + + # Phase 5 — new Next.js UI alongside the legacy Vue UI. + # Phase 5a: standalone preview (no backend wiring yet). + # Phase 5b+: depends_on redis + authentik, proxies to cloudstack:8080. + web: + build: + context: ./web + dockerfile: Dockerfile + depends_on: + redis: + condition: service_healthy + environment: + NEXT_PUBLIC_APP_ENV: local + NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000} + NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-phase5b-local-dev-replace-me} + REDIS_URL: ${REDIS_URL:-redis://redis:6379} + AUTHENTIK_ISSUER: ${AUTHENTIK_ISSUER:-http://authentik.localhost:9000/application/o/cloudstack/} + AUTHENTIK_CLIENT_ID: ${AUTHENTIK_CLIENT_ID:-cloudstack-bff} + AUTHENTIK_CLIENT_SECRET: ${AUTHENTIK_CLIENT_SECRET:-} + CS_URL: ${CS_URL:-http://cloudstack:8080} + CS_SERVICE_APIKEY: ${CS_SERVICE_APIKEY:-} + CS_SERVICE_SECRETKEY: ${CS_SERVICE_SECRETKEY:-} + BFF_SESSION_TTL_SECONDS: ${BFF_SESSION_TTL_SECONDS:-28800} + CS_SESSION_REFRESH_MARGIN_SECONDS: ${CS_SESSION_REFRESH_MARGIN_SECONDS:-120} + ports: + - "3000:3000" + healthcheck: + test: ["CMD-SHELL", "wget -q --spider http://localhost:3000/ || exit 1"] + interval: 30s + timeout: 5s + start_period: 15s + retries: 3 + + # Phase 5b — BFF/Auth.js session store for local development. + redis: + image: redis:7-alpine + command: ["redis-server", "--appendonly", "yes"] + ports: + - "6379:6379" + volumes: + - redis-data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 5s + + # Phase 5b — local Authentik OIDC provider. + # Enable with: docker compose --profile auth up --build + authentik-postgres: + image: postgres:16-alpine + profiles: ["auth"] + environment: + POSTGRES_DB: authentik + POSTGRES_USER: authentik + POSTGRES_PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik} + volumes: + - authentik-postgres-data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U authentik -d authentik"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 20s + + authentik-server: + image: ghcr.io/goauthentik/server:2025.4 + profiles: ["auth"] + command: server + depends_on: + authentik-postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-phase5b-local-dev-insecure-secret-key} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: authentik-postgres + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik} + ports: + - "9000:9000" + - "9443:9443" + networks: + default: + aliases: + - authentik.localhost + healthcheck: + test: ["CMD-SHELL", "ak healthcheck"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 90s + + authentik-worker: + image: ghcr.io/goauthentik/server:2025.4 + profiles: ["auth"] + command: worker + depends_on: + authentik-postgres: + condition: service_healthy + redis: + condition: service_healthy + environment: + AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-phase5b-local-dev-insecure-secret-key} + AUTHENTIK_REDIS__HOST: redis + AUTHENTIK_POSTGRESQL__HOST: authentik-postgres + AUTHENTIK_POSTGRESQL__NAME: authentik + AUTHENTIK_POSTGRESQL__USER: authentik + AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_POSTGRES_PASSWORD:-authentik} + volumes: + - authentik-media:/media + - authentik-templates:/templates + +volumes: + mysql-data: + redis-data: + authentik-postgres-data: + authentik-media: + authentik-templates: diff --git a/docs/AUDIT.md b/docs/AUDIT.md new file mode 100644 index 000000000000..ccf39c3ffcc5 --- /dev/null +++ b/docs/AUDIT.md @@ -0,0 +1,315 @@ +# Apache CloudStack Fork — Codebase Audit Report + +**Date:** 2026-05-15 +**Source:** Apache CloudStack 4.23.0.0-SNAPSHOT (main branch) +**Purpose:** Full codebase assessment to guide modernization of the forked project + +--- + +## Executive Summary + +CloudStack is a **2.15M-line**, **161-module** Java/Python/Vue.js codebase with 15+ years of history. It has strong test coverage (0.92:1 Java test ratio, 336K lines of Python integration tests) and a well-modularized plugin architecture (98 plugin modules). However, it carries significant technical debt: + +- **~14 dead plugins** (~70K+ lines) integrating with discontinued products +- **Critical EOL dependencies** (Spring 5.3, Jetty 9.4, Bouncy Castle 1.70, OpenSAML 2.6) +- **Java 11 target** when Java 17+ is needed for the Spring 6 migration +- **God-class service implementations** (multiple 5K-10K line files in `server/`) +- **Insecure authentication options** still present (MD5, plaintext passwords) +- **Vendored dead code** (AngularJS, jQuery 1.7 in abandoned `tools/ngui/`) + +The fork should prioritize: dead plugin removal, dependency modernization (especially the Spring 5→6 / javax→jakarta migration), and incremental refactoring of the `server/` module. + +--- + +## 1. Codebase Size & Structure + +### Lines by Language + +| Language | Files | Lines | % of Total | +|----------|-------|-------|------------| +| Java | 7,799 | 474,672 | 22.1% | +| Python | 640 | 365,452 | 17.0% | +| Vue | 387 | 150,335 | 7.0% | +| XML | 588 | 96,042 | 4.5% | +| JavaScript | 211 | 70,829 | 3.3% | +| SQL | 221 | 36,237 | 1.7% | +| Shell | 258 | 27,557 | 1.3% | +| Other (CSS, MD, YAML, Groovy, Properties, HTML) | — | ~34,500 | 1.6% | +| **Total** | — | **~2,151,000** | | + +### Module Structure + +- **161 Maven modules** (pom.xml files) +- **98 plugin modules** across 22 categories +- **Production Java:** 273K lines | **Test Java:** 253K lines (0.92:1 ratio) +- **Python integration tests:** 336K lines (Marvin framework) + +### Top-Level Directory Map + +| Directory | Size | Role | +|-----------|------|------| +| `plugins/` | 27 MB | Plugin architecture (hypervisors, networking, storage, auth, etc.) | +| `server/` | 16 MB | Management server core — largest single module (310K Java lines) | +| `test/` | 14 MB | Python/Marvin integration tests | +| `engine/` | 14 MB | Orchestration, schema/DB, storage engine, userdata | +| `ui/` | 12 MB | Vue 3 + Ant Design web UI | +| `api/` | 11 MB | Public API command/response definitions | +| `tools/` | 4.6 MB | Marvin, CLI tools, appliance builder, **dead ngui prototype** | +| `framework/` | 3.2 MB | DB, Spring, clustering, jobs, events, security, quota | +| `systemvm/` | 3.1 MB | System VM (CPVM/SSVM) agent code | +| `core/` | 3.1 MB | Agent↔server message types | +| `services/` | 2.4 MB | Console proxy (VNC/RDP), secondary storage agent | +| `utils/` | 1.9 MB | Shared utilities | +| `scripts/` | 1.8 MB | Host-level shell scripts | +| `vmware-base/` | 924 KB | VMware vSphere SDK wrappers | +| `setup/` | 556 KB | DB schema creation & upgrade scripts | +| `usage/` | 388 KB | Usage metering server | +| `agent/` | 344 KB | Hypervisor host agent launcher | +| `extensions/` | 48 KB | Stubs only (Hyper-V, MaaS, Proxmox) | +| `cloud-cli/` | 28 KB | Legacy CLI (dead) | +| `quickcloud/` | 8 KB | Dead quick-start scripts | + +### Largest Source Files (Refactoring Targets) + +| Lines | File | Notes | +|-------|------|-------| +| 14,847 | `tools/ngui/static/js/lib/angular.js` | Vendored AngularJS — delete entirely | +| 10,068 | `server/.../UserVmManagerImpl.java` | God class — VM lifecycle | +| 9,515 | `server/.../ConfigurationManagerImpl.java` | God class — system config | +| 9,404 | `tools/ngui/static/js/lib/jquery-1.7.2.js` | Vendored jQuery — delete entirely | +| 7,823 | `plugins/hypervisors/vmware/.../VmwareResource.java` | VMware hypervisor handler | +| 6,699 | `engine/orchestration/.../VirtualMachineManagerImpl.java` | VM state machine | +| 6,689 | `plugins/hypervisors/kvm/.../LibvirtComputingResource.java` | KVM hypervisor handler | +| 6,484 | `server/.../NetworkServiceImpl.java` | Network service | +| 5,994 | `server/.../ManagementServerImpl.java` | Core management server | +| 5,878 | `server/.../ApiResponseHelper.java` | API serialization | + +--- + +## 2. Dependency Health + +### Critical — EOL / Known CVEs + +| Dependency | Current Version | Issue | Action | +|------------|----------------|-------|--------| +| **Spring Framework** | 5.3.26 | EOL (Dec 2024). No security patches. | Migrate to Spring 6.x (requires Java 17+, javax→jakarta) | +| **Jetty** | 9.4.58 | EOL. No security fixes. | Migrate to Jetty 11/12 (align with Jakarta) | +| **Bouncy Castle** | 1.70 (jdk15on) | jdk15on deprecated; CVEs between 1.70 and 1.78+ | Upgrade to 1.78+ (jdk18on) | +| **OpenSAML** | 2.6.6 | Ancient/EOL. Known vulnerabilities. | Upgrade to 4.x (major rewrite of SAML2 plugin) | +| **OWASP ESAPI** | 2.1.0.1 | Multiple CVEs | Upgrade to 2.5.x | +| **JSch** | 0.1.55 | Abandoned upstream | Replace with `com.github.mwiede:jsch` 0.2.x or Apache MINA SSHD | +| **Apache CXF** | 3.2.14 | EOL | Upgrade to 4.x | +| **commons-httpclient** | 3.1 | Deprecated since 2011 | Replace with HttpClient 5.x | +| **commons-fileupload** | 1.4 | CVE-2023-24998 (DoS) | Replace with commons-fileupload2-jakarta | +| **Kafka clients** | 2.7.0 | EOL, multiple CVEs | Upgrade to 3.7+ | + +### High — Significantly Behind + +| Dependency | Current | Latest | Notes | +|------------|---------|--------|-------| +| Jackson | 2.13.3 | 2.17+ | Security fixes missed | +| Groovy | 2.4.17 | 4.x | EOL | +| Axis2 | 1.6.4 | — | Unmaintained; used for VMware SOAP | +| Ehcache | 2.6.11 | 3.x | EOL | +| AWS SDK | v1 1.12.795 | v2 | v1 in maintenance mode | +| Guava | 31.1 | 33+ | Security/bug fixes | +| Google Tink | 1.7.0 | 1.14+ | Behind 7 major versions | +| Log4j 2 | 2.19.0 | 2.24+ | Should update | + +### Build Tooling + +| Tool | Current | Latest | Priority | +|------|---------|--------|----------| +| maven-surefire-plugin | 2.22.2 | 3.x | High (JDK compat) | +| maven-failsafe-plugin | 2.22.2 | 3.x | High | +| maven-compiler-plugin | 3.8.1 | 3.13+ | Medium | +| Checkstyle lib | 8.18 | 10.x | Medium | +| OWASP Dependency-Check | 7.4.4 | 10.x | High (DB schema) | + +### Code Quality Configuration + +- **Checkstyle:** Active on `validate` phase with custom `cloud-style.xml` +- **SpotBugs:** Disabled by default; `failOnError=false` +- **PMD:** Active with custom rules; `failOnViolation=false` +- **JaCoCo:** Only in `quality` profile; **0% threshold** (not enforced) +- **SonarCloud:** Configured for Apache org + +--- + +## 3. Subsystem Inventory + +### Hypervisor Plugins + +| Plugin | Location | Java Lines | Tests | Status | +|--------|----------|-----------|-------|--------| +| **KVM/libvirt** | `plugins/hypervisors/kvm` | 58,851 | 54 files | Active | +| **VMware vSphere** | `plugins/hypervisors/vmware` + `vmware-base/` | 44,085 | 7 files | Active | +| **XenServer/XCP-ng** | `plugins/hypervisors/xenserver` | 25,189 | 23 files | Active | +| **Simulator** | `plugins/hypervisors/simulator` | 9,840 | 0 | Active (CI) | +| **External** | `plugins/hypervisors/external` | 3,878 | 4 files | Active | +| **OVM3 (Oracle VM 3)** | `plugins/hypervisors/ovm3` | 14,945 | 23 files | Abandoned (product EOL) | +| **Baremetal** | `plugins/hypervisors/baremetal` | 8,290 | 0 | Neglected | +| **Hyper-V** | `plugins/hypervisors/hyperv` | 4,392 | 1 file | Abandoned | +| **OVM (Oracle VM 2)** | `plugins/hypervisors/ovm` | 2,733 | 0 | Dead | +| **UCS (Cisco)** | `plugins/hypervisors/ucs` | 2,171 | 0 | Dead | + +### Network Plugins + +| Plugin | Location | Java Lines | Status | +|--------|----------|-----------|--------| +| **Tungsten Fabric** | `plugins/network-elements/tungsten` | 35,620 | Active | +| **NSX** | `plugins/network-elements/nsx` | 7,953 | Active | +| **Netris** | `plugins/network-elements/netris` | 9,432 | Active | +| **OVS** | `plugins/network-elements/ovs` | 3,850 | Active | +| **Internal LB** | `plugins/network-elements/internal-loadbalancer` | 3,182 | Active | +| **VXLAN** | `plugins/network-elements/vxlan` | 447 | Active | +| **DNS Notifier** | `plugins/network-elements/dns-notifier` | 120 | Active | +| **NetScaler** | `plugins/network-elements/netscaler` | 9,237 | Neglected | +| **Palo Alto** | `plugins/network-elements/palo-alto` | 4,430 | Neglected | +| **Elastic LB** | `plugins/network-elements/elastic-loadbalancer` | 1,996 | Neglected | +| **Nicira NVP** | `plugins/network-elements/nicira-nvp` | 12,505 | Dead (replaced by NSX) | +| **Juniper Contrail** | `plugins/network-elements/juniper-contrail` | 11,076 | Dead (replaced by Tungsten) | +| **BigSwitch** | `plugins/network-elements/bigswitch` | 6,771 | Dead | +| **Cisco VNMC** | `plugins/network-elements/cisco-vnmc` | 5,838 | Dead | +| **OpenDaylight** | `plugins/network-elements/opendaylight` | 4,733 | Dead | +| **Brocade VCS** | `plugins/network-elements/brocade-vcs` | 3,924 | Dead | +| **GloboDNS** | `plugins/network-elements/globodns` | 2,144 | Dead | +| **Stratosphere SSP** | `plugins/network-elements/stratosphere-ssp` | 2,086 | Dead | + +### Storage Plugins — Volume (Primary) + +| Plugin | Java Lines | Status | +|--------|-----------|--------| +| **StorPool** | 8,558 | Active | +| **ScaleIO/PowerFlex** | 6,966 | Active | +| **NetApp ONTAP** | 6,740 | Active | +| **LINSTOR** | 5,317 | Active | +| **Primera/3PAR** | 3,862 | Neglected | +| **Adaptive** | 2,663 | Active | +| **Flash Array** | 2,270 | Active | +| **Default (NFS/Local)** | 1,430 | Active | +| **SolidFire** | 5,231 | Dead | +| **CloudByte** | 4,688 | Dead | +| **Datera** | 4,248 | Dead | +| **Nexenta** | 2,077 | Dead | + +### Storage Plugins — Image / Object / SharedFS + +All image storage (Default, S3, Swift, Sample), object storage (Cloudian, MinIO, Ceph, Simulator), and shared filesystem (StorageVM) plugins are **Active**, except Swift (Neglected). + +### Other Active Plugin Areas + +- **Authentication:** LDAP, SAML 2.0, OAuth 2.0, SHA256, PBKDF2 (all active). MD5 and plain-text are insecure and should be removed. +- **2FA:** TOTP and Static PIN (active) +- **Backup:** Networker, Veeam, NAS (all active) +- **Event Bus:** Webhook, RabbitMQ, In-Memory, Kafka (all active) +- **Integrations:** Kubernetes Service (17K lines, major feature), Cloudian, Prometheus +- **DRS:** Balanced and Condensed cluster scheduling (active, newer) +- **Quota/Billing:** Active +- **Metrics/Maintenance:** Active + +### UI Layer + +| Property | Value | +|----------|-------| +| Framework | Vue 3.2 + Ant Design Vue 3.2 | +| Location | `/ui` (387 Vue files, 102 JS files) | +| Build | Vue CLI | +| Status | Active — current and only UI | + +--- + +## 4. Dead Code — Removal Candidates + +### Immediate Removal (products/companies no longer exist) + +| Component | Path | Lines | Reason | +|-----------|------|-------|--------| +| BigSwitch BCF | `plugins/network-elements/bigswitch` | ~6,771 | Arista discontinued BCF (2020) | +| Brocade VCS | `plugins/network-elements/brocade-vcs` | ~3,924 | Broadcom killed VCS (2017) | +| Cisco VNMC | `plugins/network-elements/cisco-vnmc` | ~5,838 | ASA 1000V EOL (2017) | +| Stratosphere SSP | `plugins/network-elements/stratosphere-ssp` | ~2,086 | Company gone | +| OpenDaylight | `plugins/network-elements/opendaylight` | ~4,733 | Project stagnant | +| Juniper Contrail | `plugins/network-elements/juniper-contrail` | ~11,076 | Superseded by Tungsten plugin | +| Nicira NVP | `plugins/network-elements/nicira-nvp` | ~12,505 | Superseded by NSX plugin | +| GloboDNS | `plugins/network-elements/globodns` | ~2,144 | Single-company internal project | +| Oracle VM 2 | `plugins/hypervisors/ovm` | ~2,733 | EOL for years | +| Cisco UCS | `plugins/hypervisors/ucs` | ~2,171 | Not a real hypervisor plugin | +| CloudByte | `plugins/storage/volume/cloudbyte` | ~4,688 | Company gone | +| Datera | `plugins/storage/volume/datera` | ~4,248 | Company bankrupt (2020) | +| SolidFire | `plugins/storage/volume/solidfire` | ~5,231 | Product discontinued | +| Nexenta | `plugins/storage/volume/nexenta` | ~2,077 | Product sunset | +| SolidFire test | `plugins/api/solidfire-intg-test` | ~500 | Test for dead product | +| MD5 auth | `plugins/user-authenticators/md5` | ~168 | Cryptographically broken | +| Plain-text auth | `plugins/user-authenticators/plain-text` | ~63 | Security risk | +| ngui (dead UI) | `tools/ngui/` | ~25K+ | Abandoned Angular/jQuery UI prototype | +| cloud-cli | `/cloud-cli` | ~500 | Dead legacy CLI | +| quickcloud | `/quickcloud` | ~200 | Dead quick-start | +| **Total** | | **~96,000+** | | + +### Consider for Removal (product declining or plugin neglected) + +| Component | Path | Lines | Notes | +|-----------|------|-------|-------| +| Oracle VM 3 | `plugins/hypervisors/ovm3` | ~14,945 | Product EOL | +| Hyper-V | `plugins/hypervisors/hyperv` | ~4,392 | Product deprecated by Microsoft | +| Baremetal | `plugins/hypervisors/baremetal` | ~8,290 | No tests, unclear demand | +| Swift image store | `plugins/storage/image/swift` | ~363 | Niche | +| Extensions stubs | `/extensions/` | ~200 | Non-functional stubs | + +--- + +## 5. Recommended Modernization Roadmap + +### Phase 1: Low-Risk Cleanup (Weeks 1-4) + +1. **Remove dead plugins** — delete the 17 components listed above (~96K lines) +2. **Remove dead tooling** — `tools/ngui/`, `cloud-cli/`, `quickcloud/` +3. **Update Maven build plugins** — surefire/failsafe to 3.x, compiler to 3.13+ +4. **Update safe dependencies** — Guava, commons-io, commons-lang3, Jackson, Log4j2 +5. **Enable JaCoCo with real thresholds** — enforce minimum coverage +6. **Fix SpotBugs/PMD** — set `failOnError=true` / `failOnViolation=true` + +### Phase 2: Security & Dependency Modernization (Weeks 4-10) + +7. **Replace JSch** with maintained fork +8. **Remove commons-httpclient 3.1** — migrate callers to HttpClient 5 +9. **Upgrade Bouncy Castle** to jdk18on 1.78+ +10. **Upgrade OWASP ESAPI** to 2.5.x +11. **Replace commons-fileupload** with fileupload2-jakarta +12. **Upgrade Kafka client** to 3.7+ +13. **Upgrade OWASP Dependency-Check** to 10.x and run full scan + +### Phase 3: The Big Migration — Java 17 + Spring 6 + Jakarta (Weeks 10-20) + +14. **Upgrade Java target** from 11 to 17 +15. **Migrate javax → jakarta** namespace (affects entire codebase) +16. **Upgrade Spring** 5.3 → 6.x +17. **Upgrade Jetty** 9.4 → 11 or 12 +18. **Upgrade Apache CXF** 3.2 → 4.x +19. **Upgrade OpenSAML** 2.6 → 4.x (or evaluate dropping SAML) +20. **Upgrade Groovy** 2.4 → 4.x + +### Phase 4: Refactoring (Ongoing) + +21. **Break up god classes** — `UserVmManagerImpl` (10K), `ConfigurationManagerImpl` (9.5K), etc. +22. **Standardize API layer** — inconsistent patterns across commands +23. **Improve plugin SPI** — make plugin development easier +24. **Modernize UI** — update Vue 3 / Ant Design versions, improve DX +25. **Add observability** — structured logging, metrics, distributed tracing + +### Phase 5: New Capabilities + +26. **Container workloads** — expand CKS (Kubernetes Service) +27. **Modern networking** — OVN integration, eBPF +28. **Improved multi-tenancy** and RBAC +29. **API v2** — REST-native with OpenAPI spec +30. **Better developer experience** — faster builds, better docs, docker-compose dev env + +--- + +## Appendix: Dependency Inventory + +Total unique external dependencies: ~202 artifacts from 146 group IDs. +Dependencies managed centrally in root POM: ~90. +Full dependency tree available via `mvn dependency:tree`. diff --git a/docs/CLEANUP_AUDIT.md b/docs/CLEANUP_AUDIT.md new file mode 100644 index 000000000000..aa26b43565b2 --- /dev/null +++ b/docs/CLEANUP_AUDIT.md @@ -0,0 +1,213 @@ +# Project directory cleanup audit + +Read-only audit identifying stale files, orphaned tooling, and removal +candidates in the fork. Earlier phases already removed the bulk of the +historical baggage (19 dead plugins, OVM3 hypervisor module, ngui +prototype, cloud-cli, quickcloud); this is the focused second pass. + +## Executive summary + +What remains is roughly: + +1. **OVM3 leftovers** — scripts and rat-excludes pointing at directories + that no longer exist (the Java DB-compat stubs should stay). +2. **Upstream-only `tools/` subdirs** the fork won't use (transifex, + whisker, bugs-wiki, jira, eclipse, devcloud4, devcloud-kvm). +3. **~640 MB of local `target/` build output** — git-ignored but on + disk. `mvn clean` reclaims it; no commit needed. +4. **Upstream-only CI workflows** guarded by + `github.repository == 'apache/cloudstack'` that always skip in the + fork. +5. **Small orphans** like `test/bindirbak/` and stale rat-excludes. + +Highest-leverage removal: the upstream-only `tools/` subdirs plus the +OVM3 leftovers — ~50 files dropped without touching active code paths. + +--- + +## High-confidence removals + +### 1. OVM3 leftovers + +The plugin module `plugins/hypervisors/ovm3/` is gone. Keep the two +intentional DB-compat stubs (annotated `@deprecated`): +- `api/src/main/java/com/cloud/hypervisor/Hypervisor.java:57-58` — the + `HypervisorType.Ovm3` enum constant (load-bearing for DB deserialization) +- `engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeVO.java:66-85` — + the `ovm3NetworkLabel` field + +Pure orphans to delete: +- `scripts/vm/hypervisor/ovm3/` — 2 Python files, 843 lines, 32 KB. + No references anywhere; the Java agent that called them is gone. +- `framework/quota/.../Value.java:90` — outdated docstring listing + `Ovm3` as a hypervisor (cosmetic doc drift). +- `engine/schema/templateConfig.sh` contains an `ovm3` template config + string — worth checking. + +Keep the 3 test references in `ConfigurationManagerImplTest.java:304,310` +and `VirtualMachineManagerImplTest.java:435` — they validate the +deprecated compat path. + +### 2. Stale `pom.xml` rat-excludes + +Root `pom.xml:1104-1105` excludes `tools/ngui/static/bootstrap/*` and +`tools/ngui/static/js/lib/*` from rat. `tools/ngui/` was deleted in +Phase 1 — dead config. + +### 3. Local `target/` build output + +~640 MB across 70+ `target/` directories. `git ls-files | grep target/` +returns 0 tracked files — all output. Notable cruft: +- `plugins/hypervisors/kvm/target/dependencies/` ships three Bouncy + Castle versions (`bcprov-jdk15on-{1.69,1.70,1.79}.jar`) +- Two Groovy versions (2.4.17 and 4.0.24) in the same path + +Fix: `mvn clean` on the reactor. Not a commit. + +### 4. `test/bindirbak/` orphan + +Single file `test/bindirbak/cloud-run-test.in` (1.4 KB). Name suggests +"bindir backup". No references anywhere (`grep -rn "bindirbak"` returns +nothing). + +### 5. Upstream-only `tools/` subdirs + +| Path | Size | Files | Why dead in fork | +|------|------|-------|------------------| +| `tools/whisker/` | 580 KB | 4 | ASF release license aggregation; fork doesn't cut ASF releases | +| `tools/transifex/` | 16 KB | 3 | Apache uses Transifex; fork won't | +| `tools/bugs-wiki/` | 8 KB | 2 | Apache JIRA/Confluence search | +| `tools/jira/` | 4 KB | 1 | Only ref is `CHANGES.md:1057` | +| `tools/eclipse/` | 164 KB | 3 | Not in `tools/pom.xml`; nobody using Eclipse in this fork | +| `tools/devcloud4/` | 196 KB | 22 | Vagrant dev env; superseded by Helm + docker-compose | +| `tools/devcloud-kvm/` | 48 KB | 8 | Older Vagrant KVM dev env; superseded | + +Both `devcloud4` and `devcloud-kvm` are declared as modules in +`tools/pom.xml:49-50` — removing them needs a one-line edit there. + +Combined: ~1 MB, ~49 files, ~3,450 LOC. + +### 6. Upstream-only CI workflows + +Seven workflows guarded by `if: github.repository == 'apache/cloudstack'` +(always no-op in this fork): + +- `.github/workflows/build.yml` (70 lines) — needs proprietary + `shapeblue/cloudstack-nonoss` +- `.github/workflows/codecov.yml` (59 lines) — same +- `.github/workflows/ci.yml` (351 lines, 16 KB) — replaced by `fork-ci.yml` +- `.github/workflows/docker-cloudstack-simulator.yml` (65 lines) +- `.github/workflows/main-sonar-check.yml` (68 lines) — Apache SonarCloud +- `.github/workflows/sonar-check.yml` (73 lines) — same +- `.github/workflows/rat.yml` (50 lines) — clones `shapeblue/cloudstack-nonoss` +- `.github/workflows/ui.yml` (67 lines, partially guarded) + +Plus two auto-generated `.lock.yml` files for Apache-INFRA-managed AI +workflows (`issue-triage-agent.lock.yml`, `daily-repo-status.lock.yml`) +— ~100 KB each, almost certainly dead in fork (confirm). + +~10 files, ~2,900 lines, ~120 KB. + +--- + +## Medium-confidence — worth a quick check + +- **Docs drift**: `docs/AUDIT.md:150,254` references the removed + `plugins/hypervisors/ovm3`. Five other lines reference removed + network plugins (Nicira, BigSwitch, Brocade, etc.). Add a + "Resolved in Phase 1" note rather than deleting. +- **`tools/build/`** (40 KB, 4 files): `build_asf.sh` is upstream-only. + `installer/` subdirectory may or may not be live — grep first. +- **`tools/logo/`** (184 KB): contains `acsxmas.jpg` and + `apache_cloudstack.png`. Trademark concern — Apache wordmark / logo + can't be used by non-Apache derivatives without permission. +- **Pre-7.0 XenServer support scripts**: + `scripts/vm/hypervisor/xenserver/{xenserver56,xenserver56fp1,xenserver60,xenserver62,xenserver65,xcposs,xcpserver,xcpserver83}` + are 2011–2014 era. Could be a big LOC win but is a feature decision + (drop pre-7.0 XenServer support?), not pure cleanup. +- **Dockerfile variants**: `tools/docker/Dockerfile.s390x` (IBM Z) and + `Dockerfile.marvin` likely dead in fork. +- **`test/integration/broken/`** — literally named "broken". 18 files, + 272 KB of Python integration tests. Fix or remove (project decision). +- **`developer/`** top-level — 3 files (`developer-prefill.sql`, + `developer-saml.sql`, `pom.xml`). Referenced under a profile in root + `pom.xml:1300` only — looks live, but worth a build-config check. + +--- + +## Uncertain — flag for user review + +1. **`systemvm/agent/noVNC/vendor/`** (208 KB) — contains the `pako` + library (zlib in JS). Legitimate vendored dependency of noVNC for + the console proxy. +2. **`scripts/installer/windows/` exclude in `pom.xml:1078`** — + references `acs_license.rtf`, but `find scripts/installer -type d` + only shows `scripts/installer`. Either the dir was deleted and the + exclude is stale, or the file is regenerated. +3. **`systemvm/pom.xml:183` `quickcloud` profile** — matching + the deleted `quickcloud/` top-level dir. May be a vestigial profile + name for a still-useful dev launcher (sets + `mainClass=com.cloud.agent.AgentShell`). Verify before removing. +4. **`extensions/` directory** — 3 files (`HyperV/hyperv.py`, + `MaaS/maas.py`, `Proxmox/proxmox.sh`). HyperV stays (already + confirmed). MaaS and Proxmox stubs may or may not be on the roadmap. +5. **`.github/workflows/*.lock.yml`** — auto-generated by `gh-aw`, tied + to Apache INFRA AI workflow infrastructure. Almost certainly + inactive in the fork but the imports point at `apache/.github/gh-aw`. +6. **57 files contain `svn://svn.lab.vmops.com`** in shebang/header + comments — pre-Citrix-acquisition VMOps SVN URLs from 2010. + Cosmetic dead `$Id$` SVN keywords. Not removal candidates. + +--- + +## Recommended cleanup PRs + +### PR 1 — "Drop OVM3 leftovers" (lowest risk, ~5 min review) + +- Delete `scripts/vm/hypervisor/ovm3/` (2 files, 843 lines) +- Remove dead rat-excludes in root `pom.xml:1104-1105` (ngui) +- Fix `framework/quota/.../Value.java:90` docstring to drop "Ovm3" +- Add `## Resolved in Phase 1` notes to `docs/AUDIT.md` for removed + plugins +- Test: `mvn -P quality -DskipTests=false test` passes unchanged + +### PR 2 — "Remove upstream-only `tools/` modules" (low risk, ~1 KLOC) + +- Delete `tools/whisker/`, `tools/transifex/`, `tools/bugs-wiki/`, + `tools/jira/`, `tools/eclipse/`, `tools/devcloud4/`, + `tools/devcloud-kvm/` +- Edit `tools/pom.xml` to drop `devcloud4` and + `devcloud-kvm` +- Update `CHANGES.md:1057` (only ref to `tools/jira/jira-changes.py`) +- Verify `pom.xml:1106` `tools/transifex/.tx/config` exclude isn't + needed after deletion +- ~49 files, ~3,450 LOC + +### PR 3 — "Trim upstream-only CI workflows" (low risk, isolated to `.github/`) + +- Delete `build.yml`, `codecov.yml`, `ci.yml`, + `docker-cloudstack-simulator.yml`, `main-sonar-check.yml`, + `sonar-check.yml`, `rat.yml` (all guarded by Apache repo check) +- Decide on `.lock.yml` / `md` Apache INFRA agent workflows +- Keep `fork-ci.yml`, `pre-commit.yml`, `codeql-analysis.yml`, + `merge-conflict-checker.yml`, `stale.yml`, `ui.yml` +- ~10 files, ~2,900 lines, ~120 KB + +### PR 4 — "Misc orphans" (low risk, mop-up) + +- Delete `test/bindirbak/` (1 file) +- Delete `test/integration/broken/` *if* user confirms tests are not + being fixed (18 files, 272 KB) +- Decide on `tools/docker/Dockerfile.s390x` and `Dockerfile.marvin` +- XenServer pre-7.0 script subdirs — bigger feature decision; own PR + +### Not a PR — local hygiene + +- `mvn clean` on the reactor reclaims ~640 MB of `target/` output. + All paths are in `.gitignore`; no commit needed. + +--- + +Total tracked file removal across PRs 1–4: roughly **80–100 files, +~7,500 lines**, with the largest single chunk being the auto-generated +`.lock.yml` workflows. diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 000000000000..0df99702e203 --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,127 @@ +# Deployment + +The fork ships with a `Dockerfile`, `docker-compose.yml` for local +development, and a Helm chart at `deploy/helm/cloudstack-management/`. +All three leverage the Phase 4 observability surface (health probes, +metrics endpoint, structured logging, distributed tracing). + +## Docker + +Build the image: + +```bash +docker build -t cloudstack-management:dev . +``` + +Run with an external MySQL: + +```bash +docker run --rm -p 8080:8080 -p 8443:8443 \ + -e CLOUDSTACK_LOG_FORMAT=json \ + -e DB_HOST=mysql.example.internal \ + -e DB_USER=cloud -e DB_PASSWORD=cloud \ + cloudstack-management:dev +``` + +The image: + +- Runs as non-root user `cloud` (UID 1000) +- Uses `tini` as PID 1 for proper signal handling +- Has a `HEALTHCHECK` against `/client/health/live` +- Exposes ports 8080 (HTTP) and 8443 (HTTPS) +- Config lives at `/etc/cloudstack/management/` (mount your own + `server.properties` / `db.properties` to override) + +## Local development with docker-compose + +```bash +docker compose up --build +``` + +This starts MySQL 8 and the management server. The compose file uses +the readiness probe (`/client/health/ready`) — the cloudstack service +won't be marked healthy until the database is reachable. + +Browse to: +- http://localhost:8080/client — UI +- http://localhost:8080/client/metrics — Prometheus scrape target +- http://localhost:8080/client/health/ready — readiness + +## Kubernetes via Helm + +The chart at `deploy/helm/cloudstack-management/` is production-shaped: + +- Liveness probe on `/health/live` (restart trigger) +- Readiness probe on `/health/ready` (traffic routing) +- Startup probe with a generous failure threshold (CloudStack boot + is slow — ~2 minutes) +- Non-root pod security context (UID/GID 1000, capability drop ALL) +- Optional `ServiceMonitor` for the Prometheus Operator +- Pod annotations for simple Prometheus scrape config +- Ingress template for HTTPS exposure with cert-manager +- Configurable JVM heap, OpenTelemetry exporter, log format + +### Install + +```bash +helm install cloudstack ./deploy/helm/cloudstack-management \ + --namespace cloudstack --create-namespace \ + --set image.repository=ghcr.io/d4m14ndx/cloudstack-management \ + --set image.tag=v0.1.0 \ + --set database.host=mysql-primary.db.svc.cluster.local \ + --set database.existingSecret=cloudstack-db-creds \ + --set database.existingSecretKey=password \ + --set logFormat=json \ + --set serviceMonitor.enabled=true \ + --set serviceMonitor.labels.release=kube-prometheus-stack +``` + +### Enable OpenTelemetry tracing + +```bash +helm upgrade cloudstack ./deploy/helm/cloudstack-management \ + --reuse-values \ + --set tracing.enabled=true \ + --set tracing.endpoint=http://otel-collector.observability:4318 \ + --set tracing.samplerRatio=0.05 +``` + +### Override the embedded config + +The container ships with a default `server.properties` and +`db.properties`. To override: + +```yaml +configOverride: + enabled: true + serverPropertiesConfigMap: my-cloudstack-server-config + dbPropertiesConfigMap: my-cloudstack-db-config +``` + +Where the ConfigMaps each have a single key matching the filename +(`server.properties` and `db.properties` respectively). + +### Resource sizing + +The chart defaults to 1 CPU request and 1 Gi memory request with a +4 Gi limit. CloudStack's heap settings (`javaOpts`) and the container +memory limit should be set together — leave 25% headroom for +Metaspace, off-heap buffers, and code cache. Example for a busy +deployment: + +```yaml +resources: + requests: { cpu: 2, memory: 4Gi } + limits: { memory: 8Gi } +javaOpts: "-Xmx6g -Xms2g" +``` + +### Multi-replica deployment caveats + +CloudStack supports horizontal scaling of the management server, but +expects a shared DB and shared NFS/secondary storage. The chart sets +`replicaCount` and uses a standard Deployment — for multi-replica +clustered deployment you should also configure CloudStack's +`management.network.cidr` and cluster ID in `server.properties` (via +the configOverride above) and run all replicas pointing at the same +DB. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 000000000000..2dd301bf535c --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,198 @@ +# Development guide + +Practical notes for working on the fork. Apache upstream contribution +guidance lives in `CONTRIBUTING.md`; this file documents how to build, +test, and extend this particular fork. + +## Prerequisites + +| Tool | Version | +|------|---------| +| JDK | 21 (Temurin, OpenJDK) | +| Maven | 3.9+ | +| Python | 3.10+ (for Marvin integration tests) | +| MySQL | 8.0+ (for DB tests and runtime) | + +On macOS: + +```bash +brew install openjdk@21 maven +export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home +``` + +On Ubuntu 24.04: + +```bash +sudo apt-get install -y openjdk-21-jdk maven mysql-server python3 python3-pip +``` + +## Build + +Full reactor, skipping tests (fastest for iterating): + +```bash +mvn -B -ntp install -DskipTests -T1C +``` + +With tests (~3 minutes on a modern laptop): + +```bash +mvn -B -ntp install -T1C +``` + +Just one module and its dependencies: + +```bash +mvn -B -ntp install -DskipTests -pl server -am +``` + +Resume after a partial failure: + +```bash +mvn -B -ntp install -DskipTests -rf :cloud-server +``` + +## Testing + +Unit tests use JUnit 4 (older modules) and JUnit 5 (newer code). Both +run via Surefire. + +Run all server-module unit tests: + +```bash +mvn -B -ntp test -pl server +``` + +Run a single test class: + +```bash +mvn -B -ntp test -pl server -Dtest=ConfigurationValueValidatorTest \ + -Dsurefire.failIfNoSpecifiedTests=false +``` + +Run a single method: + +```bash +mvn -B -ntp test -pl server \ + -Dtest=ConfigurationValueValidatorTest#validIp4AcceptedForIpConfig \ + -Dsurefire.failIfNoSpecifiedTests=false +``` + +Surefire reports land at `/target/surefire-reports/*.txt`. + +### Marvin (integration tests) + +Marvin integration tests require a running simulator. See the upstream +docs in `tools/marvin/README.md`. They run in CI via +`.github/workflows/ci.yml` against a Simulator-backed datacenter. + +## Code quality checks + +Local advisory runs (won't fail the build): + +```bash +mvn -B -ntp spotbugs:spotbugs -DskipTests # writes target/spotbugsXml.xml +mvn -B -ntp pmd:pmd -DskipTests # writes target/pmd.xml +mvn -B -ntp -P quality \ + org.owasp:dependency-check-maven:check \ + -DskipTests -DfailBuildOnCVSS=11 # writes target/dependency-check-report.html +``` + +CI runs all three on every PR and uploads the reports as artifacts. + +## CI + +Two workflows that matter for the fork: + +| Workflow | Trigger | What it does | +|----------|---------|--------------| +| `fork-ci.yml` | push/PR | Build + unit tests (required) + SpotBugs/PMD/OWASP (advisory) | +| `build.yml` | push/PR | Disabled on the fork; runs only on `apache/cloudstack` | + +The upstream `ci.yml` (Marvin simulator integration tests) is gated to +`apache/cloudstack` because it depends on the proprietary +`shapeblue/cloudstack-nonoss` repo. To run integration tests on the fork +locally, follow `tools/marvin/README.md`. + +## Project layout + +The codebase is a large Maven multi-module reactor. The most relevant modules: + +| Path | Purpose | +|------|---------| +| `api/` | Public API command/response definitions (`BaseCmd` and friends) | +| `server/` | Management server core (3,170+ unit tests live here) | +| `engine/orchestration/` | VM/network lifecycle orchestration | +| `engine/schema/` | DB schema VOs and DAOs | +| `framework/` | Cross-cutting: DB, Spring, jobs, events, security | +| `plugins/hypervisors/{kvm,vmware,xenserver,...}/` | Hypervisor integrations | +| `plugins/network-elements/` | Network providers (NSX, OVS, OpenDaylight, etc.) | +| `plugins/storage/{volume,image,object}/` | Storage providers | +| `services/console-proxy/` | VNC/RDP console proxy | +| `client/` | Management server WAR assembly | +| `ui/` | Vue 3 web UI | +| `tools/marvin/` | Python integration test framework | +| `utils/` | Shared utilities | + +## God class decomposition pattern + +We're incrementally carving down large `*ManagerImpl` classes. The +established pattern: + +1. **Identify a pure helper** — no DAO calls, no field state. Logger and + constants are fine; static utility imports are fine. +2. **Move it to a focused static utility class** (e.g. + `ConfigurationValueValidator` for configuration concerns). +3. **Keep the original instance method as a one-line delegating wrapper** + so any subclasses or Mockito spies in existing tests continue working. +4. **Add focused unit tests** for the new utility class — they don't + need to bootstrap Spring. + +See commits `98b13ee`, `4e89292`, `4d5d955` for examples of this pattern. + +When the orchestration of helpers must stay on the instance (e.g. +because tests spy on the building blocks), do that — only move pure +leaves. + +## Observability hooks + +The management server exposes a modern operational surface. See +`docs/OBSERVABILITY.md` for details. + +| Endpoint | Use | +|----------|-----| +| `/health/live` | Kubernetes liveness probe | +| `/health/ready` | Kubernetes readiness probe | +| `/metrics` | Prometheus scrape (JVM + process + HTTP) | + +Environment-driven settings: + +| Variable | Purpose | +|----------|---------| +| `CLOUDSTACK_LOG_FORMAT=json` | ECS-formatted JSON logs | +| `OTEL_EXPORTER_OTLP_ENDPOINT=...` | OpenTelemetry trace export | +| `OTEL_TRACES_SAMPLER_ARG=0.1` | Sampling rate (0.0–1.0) | + +## Common pitfalls + +- **Mockito spies and static delegations**: if you replace an instance + method with a call to a static helper, any existing test that + `doReturn(...).when(spy).theInstanceMethod(...)` will silently no-op + because the spy isn't intercepted. Keep the instance method as a + delegating wrapper. +- **DB-backed validations**: methods that touch DAOs aren't pure. Extract + the value-shape check as a pure helper that returns an error message, + and keep the DB lookup in the instance method. +- **`getCidrSize(String)` in NetUtils**: takes a *netmask* string, not a + CIDR with `/N`. Don't confuse with parsing the prefix length from + `"1.2.3.0/24"`. Either inline `Integer.parseInt(cidr.split("/")[1])` + or check whether you actually want netmask conversion. +- **Jakarta vs javax**: this fork is on Jakarta EE 9+ (`jakarta.*`). + Only Java SE packages (`javax.naming`, `javax.crypto`, `javax.net`, + `javax.management`, `javax.script`, `javax.sql`, most of `javax.xml`) + remain on the `javax` namespace. + +## Reporting bugs / proposing changes + +Open issues and PRs in this fork's repository. For upstream issues, +file at . diff --git a/docs/JAVA21_COMPAT_SCOUT.md b/docs/JAVA21_COMPAT_SCOUT.md new file mode 100644 index 000000000000..83a934cac757 --- /dev/null +++ b/docs/JAVA21_COMPAT_SCOUT.md @@ -0,0 +1,135 @@ +# Java 21 Compatibility Scout + +Date: 2026-05-22 +Branch: java21-compat-scout +Base: 78d2c3b3f5 +Integrated: modernize-2026 + +## Scope + +This note records the Java 21 compatibility scout findings from +`docs/NON_WEB_BACKEND_CLEANUP_JAVA21_AUDIT.md`. It excludes baseline config and +documentation files owned by the java21-baseline lane. + +## Mechanical Fixes Applied + +Replaced low-risk deprecated reflective `Class.newInstance()` calls with +`getDeclaredConstructor().newInstance()` where existing code already handled +reflection failures through local configuration/runtime error paths: + +- `services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java` +- `services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/DownloadManagerImpl.java` +- `services/secondary-storage/server/src/main/java/org/apache/cloudstack/storage/template/UploadManagerImpl.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java` +- `utils/src/main/java/com/cloud/utils/component/ComponentContext.java` +- `core/src/main/java/com/cloud/resource/RequestWrapper.java` +- `server/src/main/java/com/cloud/api/ApiAsyncJobDispatcher.java` +- `plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java` +- `plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java` +- `server/src/main/java/com/cloud/api/auth/APIAuthenticationManagerImpl.java` +- `server/src/main/java/com/cloud/api/ApiAddressVlanResponseServiceImpl.java` +- `framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java` +- `engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java` + +Remaining deprecated reflective construction sites are test-only: + +- `plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVifDriverTest.java` +- `framework/db/src/test/java/com/cloud/utils/db/ElementCollectionTest.java` + +Other `newInstance()` hits in Java files are either constructor invocation, +factory APIs such as XML/JAXB factories, `Array.newInstance`, or already use +`getDeclaredConstructor().newInstance()`. + +## Internal JDK APIs + +Resolved during the Java 21 baseline integration: + +- `services/console-proxy/rdpconsole/src/main/java/rdpclient/ntlmssp/CryptoAlgos.java` + now uses Bouncy Castle `MD4Digest` instead of `sun.security.provider.MD4`. +- `services/console-proxy/rdpconsole/src/main/java/streamer/apr/AprSocketWrapperImpl.java` + now parses the peer certificate with `CertificateFactory` and + `X509Certificate`. +- `server/src/main/java/com/cloud/api/ApiDirectDownloadCertificateResponseServiceImpl.java` + now uses the public `X509Certificate` API. +- `server/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java` + now uses the public `X509Certificate` API. + +Post-merge source search found no remaining non-web Java references to +`sun.security.*`, `X509CertImpl`, or `sun.security.provider.MD4`. + +## `com.sun.net.httpserver` + +Console proxy and Prometheus exporter depend on the JDK HTTP server API: + +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxImageHandler.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyAjaxHandler.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyBaseServerFactoryImpl.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyThumbnailHandler.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyResourceHandler.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyCmdHandler.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyServerFactory.java` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxySecureServerFactoryImpl.java` +- `plugins/integrations/prometheus/src/main/java/org/apache/cloudstack/metrics/PrometheusExporterServerImpl.java` + +This is not necessarily a Java 21 compile blocker, but it is a portability and +module-surface risk. Treat replacement or containment as a separate server +slice. + +## Finalization + +Object finalization-style cleanup remains in: + +- `engine/orchestration/src/main/java/com/cloud/agent/manager/DirectAgentAttache.java` +- `engine/orchestration/src/main/java/com/cloud/agent/manager/ConnectedAgentAttache.java` +- `framework/db/src/main/java/com/cloud/utils/db/SearchBase.java` +- `framework/db/src/main/java/com/cloud/utils/db/ConnectionConcierge.java` +- `framework/db/src/main/java/com/cloud/utils/db/TransactionLegacy.java` + +Search also finds domain methods named `finalize(Network, boolean)` in network +redundancy code; those are not `Object.finalize()` cleanup overrides. + +## JVM Flags + +`--add-opens`, `--add-exports`, and `-noverify` references remain in baseline +configuration areas and runtime packaging: + +- `pom.xml` +- `Dockerfile` +- `developer/pom.xml` +- `plugins/user-authenticators/ldap/pom.xml` +- `packaging/systemd/cloudstack-management.default` +- `packaging/systemd/cloudstack-usage.default` + +The baseline-owned files were intentionally not edited in this scout branch. + +Mockito inline mocking is now handled by the Java 21 baseline. Surefire and +Failsafe resolve `mockito-core` with `maven-dependency-plugin:properties` and +attach it with `-javaagent`, matching Mockito's Java 21 guidance and avoiding +dynamic self-attach failures in focused module tests. + +## Verification Notes + +`git diff --check` passed. + +The original scout branch targeted compile was blocked by the old +`aspectjweaver:1.8.13` artifact. The Java 21 baseline now aligns both +`aspectjtools` and `aspectjweaver` with `${cs.aspectjrt.version}` (`1.9.19`). + +Post-merge verification under OpenJDK 21: + +```bash +JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home mvn -B -ntp install -DskipTests -T4 +JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home mvn -B -ntp -pl plugins/storage/volume/ontap test -T1 +JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home mvn -B -ntp -pl server -Dtest=ApiDirectDownloadCertificateResponseServiceImplTest,DirectDownloadManagerImplTest test -T1 +``` + +All passed. + +## Suggested Next Slices + +1. Finish the remaining test-only `Class.newInstance()` replacements. +2. Plan explicit cleanup replacements for `finalize()` users, starting with + `DirectAgentAttache` and `ConnectedAgentAttache`. +3. Decide whether to replace, wrap, or keep the JDK `com.sun.net.httpserver` + dependency in console proxy and Prometheus exporter. diff --git a/docs/MICROSERVICES_PLAN.md b/docs/MICROSERVICES_PLAN.md new file mode 100644 index 000000000000..47a29ef31386 --- /dev/null +++ b/docs/MICROSERVICES_PLAN.md @@ -0,0 +1,129 @@ +# CloudStack microservices roadmap + +Investigation summary for the multi-phase split of the management-server +monolith into separately-deployable services. Phase 4 (god-class +decomposition into Spring components) is foundational; this document +covers what comes next. + +## Headline + +The Vue 3 UI at [`ui/`](../ui) is **already a fully separated SPA** — +a `cloudstack-ui` RPM target and a Vue + nginx Dockerfile already exist +in the tree. The management server simply also happens to serve the +built `ui/dist/` as static assets at the root context. Splitting the UI +into its own deployable is a **trivial-to-small** packaging/topology +change, not a code rewrite. The only real engineering work is making +the reverse-proxy / CORS / session-cookie story production-grade. + +## Module map (~142 Maven modules) + +| Group | Modules | Role | +|---|---|---| +| Core API surface | `api`, `core`, `utils`, `engine/api`, `engine/components-api` | DTOs, command objects, SPI | +| Orchestration / server | `server`, `engine/orchestration`, `engine/service`, `engine/schema` | The monolith — VM/Network/Storage/Cluster managers live in `server/` | +| Storage engines | `engine/storage/{cache,configdrive,datamotion,image,object,snapshot,volume}` | Already plugin-shaped | +| UI | `ui/` (Vue, not a Maven module) + `client/` (Jetty bootstrap + WAR) | Web UI + the JSVC daemon | +| Framework | `framework/{agent-lb,ca,cluster,config,db,direct-download,events,extensions,ipc,jobs,managed-context,quota,rest,security,spring/*}` | Shared infra | +| Standalone services | `usage/`, `services/console-proxy/{server,rdpconsole}`, `services/secondary-storage/{controller,server}` | Already separate processes today | +| Agent | `agent/` | Runs on hypervisor hosts | +| Plugins (84) | `plugins/{acl,affinity-group-processors,alert-handlers,api,backup,ca,database,dedicated-resources,deployment-planners,drs,event-bus,ha-planners,hypervisors,integrations,maintenance,metrics,network-elements,outofbandmanagement-drivers,storage-allocators,storage,user-authenticators,user-two-factor-authenticators}` | Loaded into management server via Spring | + +## UI: how it's currently served + +- **Code**: [`ui/`](../ui) — Vue 3 + Vue CLI 4 + webpack 4 + Ant Design Vue. + 472 JS/Vue source files, ~6.6 MB `src/`, ~3.7 MB `public/`. Builds + to `ui/dist/`. +- **In Jetty**: [`client/src/main/java/org/apache/cloudstack/ServerDaemon.java:267-298`](../client/src/main/java/org/apache/cloudstack/ServerDaemon.java) + mounts a single `WebAppContext` at `/client` (the API). UI static + files are copied into the same WAR at packaging time + ([`packaging/el8/cloud.spec:283-287`](../packaging/el8/cloud.spec)): + `cp -r ui/dist/* /usr/share/cloudstack-management/webapp/`. +- **`web.xml`** ([`client/src/main/webapp/WEB-INF/web.xml:89-107`](../client/src/main/webapp/WEB-INF/web.xml)) + maps servlets for `/api/*`, `/console`, `/health/*`, `/metrics`; + everything else falls through to Jetty's default static handler. + `ServerDaemon.java:291-294` redirects `/` → `/client/`. +- **Already-existing separate deployable**: `cloudstack-ui` RPM target + ([`packaging/el8/cloud.spec:332-336`](../packaging/el8/cloud.spec)) + ships `ui/dist/` standalone. `ui/Dockerfile` produces an + `nginx:alpine` runtime image and `ui/nginx/default.conf` proxies + `/client/` to a separate management server. The standalone story is + already in the tree, just not the default. +- **API calls**: pure REST/HTTP to `${apiBase}` from a runtime-loaded + `ui/public/config.json` (symlinked from `/etc/cloudstack/ui/config.json` + in the RPM) — overridable without rebuilding. +- **Auth**: hybrid session cookie + double-submit token. + `ui/src/api/index.js:43-45` pulls `sessionkey` from `localStorage` or + the `sessionkey` cookie and appends it as a query param. JSESSIONID + is the browser's. `ui/src/store/modules/user.js:228-229,286-287` + writes the session key. **This is the constraint for the UI split**: + cookies need either same-origin to the API or a reverse-proxy that + bridges them. + +## Coupling assessment + +**Coupling is shallow.** Specifically: + +- `ServerDaemon.java:280-284` — webapp dir is configurable via + `server.properties` `webapp.dir`. Removing the UI assets produces an + API-only WAR; nothing else in `web.xml` depends on UI files. +- No backend Java code references HTML files, templates, or UI asset + paths beyond Jetty's default static-resource handler. +- No JVM-shared state between UI and backend (UI is a pure browser SPA). +- Only remaining "coupling" is the cookie domain + session story. + Three valid topologies: + 1. **Reverse proxy** (nginx / Envoy / Traefik) serving UI from `/`, + proxying `/client/*` to management. Same-origin → cookies just + work. *Preferred.* + 2. **Separate origins with CORS** — requires backend CORS config + + `SameSite=None; Secure` cookies; auditable but more moving parts. + 3. **Move auth to JWT/bearer tokens** — bigger lift, but unblocks + federated / multi-cluster deployments later. + +## Other natural microservice seams (post-UI) + +1. **Usage server** ([`usage/pom.xml`](../usage/pom.xml), `cloud-usage`). + Already a separate JVM/daemon (`cloudstack-usage.service`). Mostly a + packaging + observability + API-fronting cleanup, not an extraction. + Lowest-risk follow-up. +2. **Async-job worker pool** ([`framework/jobs/`](../framework/jobs), + [`engine/orchestration/`](../engine/orchestration)). The hottest part + of the management server. Splitting "API frontend (stateless, replicas)" + from "job engine (workers)" unlocks horizontal scale without touching + domain code. Job queue already uses MySQL + cluster lock manager. +3. **Plugin classes as services**: + - **Event bus** (`plugins/event-bus/{inmemory,kafka,rabbitmq,webhook}`) — + events already abstracted; sidecar that publishes domain events to + external consumers. + - **Metrics/observability** (`plugins/metrics/`, + `plugins/integrations/prometheus/`) — already runs alongside; could + be lifted out behind the existing `/metrics` Prometheus endpoint. +4. **Console proxy** ([`services/console-proxy`](../services/console-proxy)). + Already a separate JVM but tightly coupled via `ConsoleProxyServlet` + in the management WAR (`web.xml:71-75`). Cleanup pass to make it + consumable as an independent service. +5. **Hypervisor connector subset** + (`plugins/hypervisors/{kvm,vmware,xenserver,hyperv,baremetal,external,simulator}`). + Long-term: each hypervisor's resource manager could become a + per-cluster scale-out service talking to the orchestrator via + gRPC/Kafka instead of in-JVM Spring beans. Major effort — defer + until Phase 4 settles the boundaries. + +## Phased roadmap + +| Phase | Scope | Depends on | Effort | +|---|---|---|---| +| **5a — UI split** | Stop bundling `ui/dist` in the management WAR. First-class `cloudstack/ui` container + Helm sub-chart with nginx reverse-proxy template. Document the same-origin reverse-proxy pattern; add CORS allowlist in `ApiServlet` for the separate-origin path. CI builds & publishes both images. | None — Phase 4 continues in parallel. | **S** | +| **5b — Stateless API frontends** | Make the management WAR cleanly horizontally scalable: audit `HttpSession` use (`ApiSessionListener` today), move session state to an external store (Redis) or switch to opaque-token auth backed by DB. Add load-balanced ingress in Helm chart. | 5a complete | **M** | +| **5c — Job engine split** | Carve `framework/jobs/` + `engine/orchestration/` async dispatcher into its own deployment. API frontends enqueue; job workers run. Use existing MySQL job queue + cluster lock manager (or upgrade to Kafka for fan-out). | 5b done; Phase 4 close to landed so service boundaries stable. | **L** | +| **5d — Usage + console-proxy as proper services** | Tighten + document the already-separate `usage` and `console-proxy` daemons. Define stable internal APIs. Helm sub-charts. | Independent — can interleave with 5c. | **M** | +| **5e — Event-driven plugin boundary** | Promote `framework/events` + `plugins/event-bus/kafka` to default for cross-service comms. External consumers can subscribe without poking the management server. Sets up future per-hypervisor / per-zone services. | 5c done so workers already emit events. | **M** | + +## Recommended first concrete action + +Land a CI workflow that builds two separate images — `cloudstack-management` +(current Dockerfile **minus** `ui/dist` copying, plus a server-side CORS +allowlist read from a property) and `cloudstack-ui` (promote +`ui/Dockerfile`) — and a Helm sub-chart pairing the two behind a single +nginx-ingress with `/` → UI and `/client/*` → management. This delivers +the UI split with zero domain-code risk and unblocks every subsequent +phase. diff --git a/docs/NON_WEB_BACKEND_CLEANUP_JAVA21_AUDIT.md b/docs/NON_WEB_BACKEND_CLEANUP_JAVA21_AUDIT.md new file mode 100644 index 000000000000..4eb25179fe6e --- /dev/null +++ b/docs/NON_WEB_BACKEND_CLEANUP_JAVA21_AUDIT.md @@ -0,0 +1,421 @@ +# Non-Web Backend Cleanup and Java 21 Audit + +**Date:** 2026-05-22 +**Branch:** `modernize-2026` +**Scope:** Everything outside `web/`. The new Next.js app is intentionally +excluded because it is still active in-flight work. + +This document consolidates the non-web findings from the unused-code and +duplication audit, then adds a Java 21 modernization pass. Treat it as the +backend cleanup queue, not as proof that every candidate is safe to delete +without tests. CloudStack still uses Spring wiring, API command discovery, +reflection, generated/serialized DTOs, plugin loading, and long-lived database +compatibility paths. + +## CloudStack 5.0 Compatibility Stance + +This modernization project is framed as **CloudStack 5.0**, not as a strict +4.x-compatible continuation. Apache CloudStack 4.x can keep evolving in +parallel for compatibility users, while this fork is allowed to remove baggage +that has accumulated since the 4.0 era. + +Consequences for this audit: + +- API, plugin, CLI, packaging, and internal interface breaks are acceptable when + they materially simplify the platform or unlock the modernization roadmap. +- Breaking changes must be documented with migration notes. +- Where the capability is still relevant, provide an equivalent or replacement + API/workflow rather than keeping the old interface shape by default. +- Database compatibility stubs should be evaluated case by case: keep them for + practical upgrade/import paths, but do not treat every 4.x artifact as + load-bearing forever. +- Cleanup slices should prefer the CloudStack 5.0 design over preserving 4.x + behavior unless there is a current product reason to keep the behavior. + +## Current Java Baseline + +The fork has moved beyond upstream's source baseline: + +- Apache `cloudstack/main` still has `11` in + `pom.xml` as observed from the GitHub mirror and local `upstream/main`. +- Apache CloudStack 4.20 added Java 17 runtime support, and 4.22 documentation + says management server and KVM agent require Java 17. +- This fork now uses `21` in root `pom.xml` + and `.java-version` is `21`. + +The first Java 21 baseline pass has aligned build, packaging, and developer +docs. Follow-up cleanup remains: + +- Root `pom.xml`, developer POMs, LDAP tests, and systemd defaults still carry + some `-noverify`, `--add-opens`, and `--add-exports` flags. Remove only after + focused tests prove each reflective path is gone. +- Mockito inline mocking now runs through an explicit test JVM `-javaagent` + configured via `maven-dependency-plugin:properties`; this avoids Java 21 + self-attach failures while preserving static/final mocking tests. + +## Java 21 Migration Work + +### Baseline Update Slice + +1. Set `.java-version` to `21`. +2. Set root `` to `21`. +3. Change Maven compiler configuration from source/target to + `${cs.jdk.version}` unless a specific module genuinely + needs different cross-compilation behavior. +4. Remove the ONTAP module's source/target `11` override or align it with the + root property. +5. Update Docker images from `eclipse-temurin:17-*` to `21-*`. +6. Update Debian build dependencies to prefer OpenJDK 21 while keeping any + deliberate distro compatibility fallback explicit. +7. Update Java 17 references in developer and CI docs. +8. Run a full build on Java 21 before changing language idioms. + +Expected first verification: + +```bash +JAVA_HOME= mvn -T 4 -DskipTests install +JAVA_HOME= mvn -T 4 test +``` + +Status on 2026-05-22: the Java 21 baseline slice completed `.java-version`, +root compiler release, Docker, Debian, ONTAP test stack alignment, AspectJ +1.9.19 alignment, and developer/CI docs. The full reactor passed under +OpenJDK 21 with `mvn -B -ntp install -DskipTests -T4`. + +### Java 21 Compatibility Risks To Check + +- Internal JDK APIs: + - Done: `sun.security.x509.X509CertImpl` in direct-download certificate + handling and RDP console code was replaced with public certificate APIs. + - Done: `sun.security.provider.MD4` in RDP NTLM code was replaced with + Bouncy Castle `MD4Digest`. + - Still open: `com.sun.net.httpserver.*` in console proxy and Prometheus + exporter should be contained or replaced in a later server slice. +- Reflective construction: + - Done for low-risk production sites found in the Java 21 scout. Remaining + direct `Class.newInstance()` calls are test-only and tracked in + `docs/JAVA21_COMPAT_SCOUT.md`. +- Finalization: + - `DirectAgentAttache`, `ConnectedAgentAttache`, `TransactionLegacy`, + `ConnectionConcierge`, and `SearchBase` still use `finalize()` style + cleanup. Move to explicit close/cleanup or `Cleaner` where needed. +- JVM flags: + - Root `argLine`, Dockerfile, developer POMs, LDAP tests, and systemd defaults + all carry `--add-opens` / `--add-exports`. Revalidate under Java 21 and + remove only after tests prove the reflective path is gone. + - Done for Mockito: Surefire/Failsafe now attach `mockito-core` explicitly as + a Java agent so Java 21 test runs do not depend on dynamic self-attach. +- Preview features: + - Do not use Java 21 preview features in production code. Avoid string + templates, unnamed patterns, unnamed classes, scoped values, and structured + concurrency until they are final in the chosen target. + +## Unused or Dead Non-Web Code + +### High Confidence + +These are the best first cleanup candidates. + +Status update on 2026-05-22: the stale ngui RAT excludes and the smokedev +Dockerfile copy of the removed test backup directory were cleaned up in the +`backend-deadcode-stale-build-artifacts` slice. They are no longer pending +items in this queue. + +| Area | Candidate | Evidence | Verification | +|---|---|---|---| +| OVM3 leftovers | `scripts/vm/hypervisor/ovm3/` | The Java OVM3 plugin is gone; only DB compatibility enum/fields should stay. | Delete scripts, run RAT/checkstyle/build. | +| `AnnotationManagerImpl` | private `isDomainAdminAllowedType(EntityType)` | Static search found only the definition. | Remove with annotation permission tests. | +| `IndirectAgentLBServiceImpl` | private `getAllAgentBasedHostsInDc(long,long)` | Static search found only the definition. | Remove with agent LB tests. | +| `OutOfBandManagementServiceImpl` | private `getOutOfBandManagementHostLock(long)` | Static search found only the definition. | Remove with OOBM sync/lock tests. | +| `NetworkACLManagerImpl` | private `containsIpv6Cidr(List)` | Static search found only the definition. | Remove with IPv6 ACL tests. | +| `VolumeServiceImpl` | private `waitForTemplateDownloaded(...)` | Static search found only the definition. | Remove with template-to-volume tests. | +| `RolePermissionsDaoImpl` | private `updateSortOrder(...)` | Static search found only the definition. | Verify role permission reorder/move behavior. | +| `VlanDaoImpl` | private `findNextVlan(long, VlanType)` | Static search found only the definition. | Verify VLAN/public IP allocation tests. | +| `LibvirtComputingResource` | private `isSnapshotSupported()` | Static search found only the definition. | Verify KVM snapshot tests. | +| `ResourceCountDaoImpl` | private `baseSqlCountComputingResourceAllocatedToAccount` and `executeSqlCountComputingResourcesForAccount(...)` | Field and helper appear only locally. | Verify quota/resource count tests. | + +### Sensitive Candidates + +These are likely dead but should be treated with extra care. + +| Area | Candidate | Why sensitive | +|---|---|---| +| API security | `ApiDispatcher#doAccessChecks(...)` | Active checks appear to live in `ParamProcessWorker`, but this is access-control code. Trace all dispatch paths before removal. | +| API serialization | `ResponseObjectTypeAdapter#getGetMethod(...)` and `getGetMethodName(...)` | Reflection/serialization-adjacent code can be indirectly load-bearing. | +| API docs | `ApiXmlDocWriter#zipDir(...)` and `addDir(...)` | May only be used by manual doc-generation flows. | +| `NetworkServiceImpl` | private `canIpsUseOffering(...)` | Duplicate of the active method in `NetworkMigrationServiceImpl`; appears unused in `NetworkServiceImpl`, but network offering validation is high-impact. | + +### Non-Code Orphans and Fork-Only Cleanup + +The earlier cleanup audit remains valid outside `web/`: + +- `tools/whisker/`, `tools/transifex/`, `tools/bugs-wiki/`, `tools/jira/`, + `tools/eclipse/`, `tools/devcloud4/`, and `tools/devcloud-kvm/` appear + upstream-process/dev-environment specific. Removing `devcloud4` and + `devcloud-kvm` also requires editing `tools/pom.xml`. +- Upstream-only Apache CI workflows guarded by + `github.repository == 'apache/cloudstack'` can be removed or archived if the + fork has its own CI. +- `scripts/vm/hypervisor/ovm3/` should be removed separately from the OVM3 DB + compatibility enum and schema fields, which should stay. +- `test/integration/broken/` should be fixed or removed by explicit project + decision. + +## Repeated Backend Code + +### Highest Value Refactor Targets + +| Pattern | Examples | Suggested extraction | Risk | +|---|---|---|---| +| IPv4 range validation | `PodServiceImpl`, `VlanServiceImpl`, related `NetworkServiceImpl` gateway/netmask checks | `Ipv4RangeValidator` or `IpRangeValidationService` | Medium: preserve exact error behavior. | +| API success/error boilerplate | Many delete/cancel commands build `SuccessResponse` or throw `ServerApiException` | `ApiCommandSuccessHandler` | Low-medium. | +| Network element API exception mapping | NetScaler and Palo Alto add/configure commands repeat exception mapping | `NetworkElementApiExecutor` | Low-medium. | +| Tungsten Fabric command boilerplate | Create/delete/list/apply commands repeat owner, command-name, event-description, list-response patterns | `BaseTungstenFabricCmd` and list response helper | Low-medium. | +| Usage DAO date binding | `UsageIPAddressDaoImpl`, `UsageVolumeDaoImpl`, `UsageStorageDaoImpl`, and related DAOs | `UsageDateRangeBinder` | Low-medium: parameter order differs. | +| Query `Pair, count>` conversion | Repeated in `QueryManagerImpl` and query services | `ListResponseBuilder.fromPair(...)` | Low. | +| `canIpsUseOffering` | `NetworkServiceImpl` and `NetworkMigrationServiceImpl` | `NetworkOfferingIpCompatibilityService` | Medium: network offering upgrade behavior. | +| Storage access group orchestration | `StorageAccessGroupServiceImpl` explicitly notes duplicated direct/orchestrator method bodies | Shared private implementation or service split | Medium: Phase 4 extraction compatibility. | +| Listener no-op methods | Agent/listener implementations repeat identical no-op bodies | Java 17+ default interface methods or `NoopAgentListener` | Medium: broad interface impact. | +| Network element lifecycle no-ops | Security group, Baremetal, VMware, DNS notifier, OpenDaylight, NSX/Netris-style elements | Default interface methods or adapter base | Medium. | +| Upgrade DAO boilerplate | Many `Upgrade*` classes repeat version/script/null migration methods | `DbUpgradeScriptLoader` or `DbUpgradeDescriptor` base | Medium-high: upgrade paths are sensitive. | +| Resource detail VOs | Many VOs duplicate `id/resourceId/name/value/display` mappings | Mapped superclass or smaller helper | High: JPA mapping risk. | + +### Existing Intentional Duplication from Prior Slices + +The codebase contains comments marking duplication introduced during safe +god-class decomposition. These should be revisited only after the extracted +services have stabilized: + +- `StorageAccessGroupService` / `StorageAccessGroupServiceImpl` direct vs + orchestrator bodies. +- `UserUpdateServiceImpl` helpers copied from `AccountManagerImpl`. +- `VlanServiceImpl`, `ZoneServiceImpl`, and `PortableIpRangeServiceImpl` + helpers copied from `ConfigurationManagerImpl`. +- `VpcOfferingCrudServiceImpl` constants copied per playbook. +- `PhysicalNetworkManagementServiceImpl` shared DAO comments. + +## Java 21 Modernization Opportunities + +The repo should not be mechanically rewritten. Use Java 21 features where they +make repeated or error-prone code smaller and clearer. + +### Pattern Matching for `instanceof` and `switch` + +Scan result: roughly 1,600 non-web `instanceof` hits. + +Good targets: + +- Command dispatch chains in secondary storage resources and storage command + handlers. +- Data object type branching in `AncientDataMotionStrategy`, + `StorageSystemDataMotionStrategy`, `SecondaryStorageServiceImpl`, and + image/volume object callbacks. +- API/entity access checks such as `DomainChecker` once tests cover each entity + branch. + +Avoid or defer: + +- Serialization/deserialization adapters. +- Branches where type checks are deliberately ordered for compatibility. +- Public API response DTOs unless tests cover wire output. + +### Records and Record Patterns + +Best candidates are private immutable holder classes, not JPA entities, API +responses, command payloads, or Gson/Jackson-reflected objects. + +Good targets: + +- Private usage parser holders: `VMInfo`, `IpInfo`, `VolInfo`, `NetworkInfo`, + `PFInfo`, `LBInfo`, `VUInfo`, `NOInfo`, `SGInfo`, `StorageInfo`. +- Small private job/context holders such as `DownloadJob`, `UploadJob`, + `ActiveTaskRecord`, `VcenterData`, `NetworkCopy`, and simple result holders. +- Test-only fixture holder classes. + +Avoid: + +- JPA `VO`/DAO model classes. +- API command/response classes. +- Agent command payloads that are serialized over the wire. +- Classes requiring mutable JavaBean setters for frameworks. + +### Sequenced Collections + +Scan result: roughly 1,500 non-web `get(0)`, `get(size() - 1)`, and related +first/last collection access hits. + +Good targets: + +- Repeated "first result after non-empty check" helpers. +- Tungsten model response builders using + `referredName.get(referredName.size() - 1)`. +- Utility classes that already require ordered lists. + +Rules: + +- Replace with `getFirst()` / `getLast()` only where the static type is a Java + 21 sequenced collection type and tests cover empty-list behavior. +- Do not hide missing empty checks; add explicit validation where needed. + +### Streams + +Scan result: 360+ non-web `Collectors.toList()` / `Collectors.toSet()` sites. + +Use `stream().toList()` only where the returned list is not mutated. It returns +an unmodifiable list, while `Collectors.toList()` historically produced a +mutable list in practice. Do not mechanically replace set collectors because +there is no `Stream.toSet()`. + +Good first targets: + +- API response ID/UUID lists that are immediately passed onward. +- Log/debug string construction. +- Query list transformations where the returned collection is read-only. + +### Text Blocks and Formatted Strings + +Scan result: 600+ non-web `StringBuilder` / `StringBuffer` construction sites. + +Good targets: + +- Long SQL strings in DAOs. +- XML snippets in KVM/libvirt command wrappers. +- Multi-line config-drive, metadata, or cloud-init templates. + +Avoid: + +- Tight loops accumulating large strings. +- Security-sensitive command-line generation until escaping rules are clear. + +### Virtual Threads + +Java 21 virtual threads are useful for blocking I/O fan-out, but they are not a +blanket replacement for CloudStack's scheduled scanners and bounded queues. + +Good candidates: + +- Short-lived executor fan-out in `IndirectAgentLBServiceImpl` setup/migration + helpers. +- Webhook delivery and alert/email sending if back-pressure remains explicit. +- Script or command wrappers that block on external processes, once timeouts and + cancellation are tested. +- IPMI/out-of-band command execution, where the driver blocks on external + process/network operations. + +Avoid first: + +- Scheduled background scanners. +- Agent task pools where queue bounds are part of flow control. +- Database transaction workers unless connection-pool pressure is explicitly + tested. +- NIO/selector loops. + +### Finalization and Resource Cleanup + +Modern Java has moved away from finalization. Before or alongside Java 21, clean +these up: + +- `DirectAgentAttache#finalize` +- `ConnectedAgentAttache#finalize` +- `TransactionLegacy#finalize` +- `ConnectionConcierge#finalize` +- `SearchBase#finalize` + +Preferred replacements are explicit lifecycle methods, `AutoCloseable`, and +`Cleaner` only where last-resort cleanup is still needed. + +## Proposed Work Packages + +### A. Java 21 Baseline + +Update build/runtime/config/docs only. Do not modernize source code in the same +branch. Acceptance: full Java 21 build and targeted module tests pass. + +### B. Decomposition Shim Removal + +Revisit the god-class decomposition slices after the extracted services have +stabilized. Many Phase 4 extractions intentionally left manager-level shims so +call sites could be migrated safely in small batches. + +For CloudStack 5.0, the cleaner end state is: + +1. Move internal call sites to the extracted service interfaces/classes. +2. Keep only genuinely public or compatibility-required facade methods on the + old manager classes. +3. Delete shim methods once all non-reflective call sites have moved. +4. Document any broken internal API paths and the replacement service location. +5. Add or update focused tests around each migrated call path before deleting + the shim. + +Suggested first targets are the extraction areas already called out above: + +- `StorageAccessGroupServiceImpl` direct/orchestrator duplicated paths. +- `UserUpdateServiceImpl` helpers copied from `AccountManagerImpl`. +- `VlanServiceImpl`, `ZoneServiceImpl`, and `PortableIpRangeServiceImpl` + helpers copied from `ConfigurationManagerImpl`. +- `VpcOfferingCrudServiceImpl` constants copied per playbook. +- `PhysicalNetworkManagementServiceImpl` DAO helper comments. + +Acceptance: each slice replaces downstream call sites with the extracted +service, proves behavior with focused tests, then removes the now-unused manager +shim or marks it as a deliberate public facade. + +### C. High-Confidence Dead Code Cleanup + +Remove private unused helpers, OVM3 scripts, and stale Java config. Acceptance: +relevant unit tests plus full compile. The stale ngui RAT excludes and the +smokedev Dockerfile copy of the removed test backup directory have already been +cleaned up. + +### D. Repeated Backend Code Extraction + +Start with cohesive, low-to-medium risk helpers: + +Status on 2026-05-22: the first backend refactor pass completed the planned +low-to-medium risk helper extractions below. Follow-up slices should now +continue from broader call-site migration, shim removal, and Java 21 idiom +modernization rather than recreating these helpers. + +1. `Ipv4RangeValidator`: extracted for pod and VLAN IPv4 range checks, with + focused validator coverage. +2. `BaseCmd#setSuccessResponse(...)`: added as the shared success-response path + for simple API commands, including dedicated-resource release commands. +3. `NetworkElementApiExecutor`: extracted for Palo Alto and NetScaler command + exception mapping. +4. `UsageDateRangeBinder`: extracted and applied across the first two usage DAO + batches. +5. `TungstenFabricAsyncCmd`: extracted shared Tungsten delete-command success + response handling. +6. `ListResponseBuilder`: extracted for query-service `Pair, count>` + response construction across the first two query-service batches. + +### E. Java 21 Idiom Passes + +Run only after Java 21 is the green baseline: + +1. Record conversions for private immutable holders. +2. Pattern matching for command/data-object dispatch. +3. Safe `stream().toList()` conversions. +4. Sequenced collection first/last cleanup. +5. Text block cleanup for SQL/XML/config literals. +6. Virtual-thread experiments behind focused tests. + +## Source Notes + +- Apache CloudStack GitHub mirror, + [`pom.xml` on `main`](https://github.com/apache/cloudstack/blob/main/pom.xml), + shows upstream `cs.jdk.version` as `11`. +- Apache CloudStack + [upgrade docs](https://docs.cloudstack.apache.org/en/latest/upgrading/upgrade/upgrade_java_17_notes.html) + state Java 17 support was added in 4.20 and CloudStack 4.22 requires Java 17 + for management server and KVM agent. +- The [OpenJDK JDK 21 project page](https://openjdk.org/projects/jdk/21/) + lists sequenced collections, record patterns, pattern matching for switch, + and virtual threads. +- Oracle's + [Java language changes summary](https://docs.oracle.com/en/java/javase/21/language/java-language-changes-summary.html) + lists record patterns and switch pattern matching as permanent Java 21 + language features. +- Mockito's + [Java 21 inline mocking guidance](https://javadoc.io/static/org.mockito/mockito-core/5.16.1/org.mockito/org/mockito/Mockito.html#0.3) + recommends explicit Java-agent setup for Maven Surefire. diff --git a/docs/OBSERVABILITY.md b/docs/OBSERVABILITY.md new file mode 100644 index 000000000000..0333d0bbc1e1 --- /dev/null +++ b/docs/OBSERVABILITY.md @@ -0,0 +1,218 @@ +# Observability + +## Distributed tracing (OpenTelemetry) + +Every incoming HTTP request to the management server is wrapped in an +OpenTelemetry `SERVER` span via `TracingFilter`. W3C trace context +(`traceparent` header) from upstream callers is honored, so traces span +across services. + +### Configure exporter + +The SDK is initialized via [OTel autoconfigure](https://opentelemetry.io/docs/zero-code/java/spring-boot-starter/), +so all standard `OTEL_*` env vars work without code changes: + +```bash +export OTEL_SERVICE_NAME=cloudstack-management +export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 +export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf +export OTEL_TRACES_EXPORTER=otlp +export OTEL_TRACES_SAMPLER=parentbased_traceidratio +export OTEL_TRACES_SAMPLER_ARG=0.1 +``` + +Defaults applied when env is missing: +- `OTEL_SERVICE_NAME=cloudstack-management` +- 10% trace sampling rate + +To **disable** trace export entirely (in-process spans still created, just not sent): + +```bash +export OTEL_TRACES_EXPORTER=none +``` + +### Excluded endpoints + +The filter skips `/health/*` and `/metrics` to avoid flooding the trace store +with low-value probe spans. + +### Add custom spans + +Anywhere in the server module: + +```java +import com.cloud.observability.TracingHolder; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Scope; + +Span span = TracingHolder.tracer().spanBuilder("vm.deploy") + .setAttribute("vm.template.id", templateId) + .startSpan(); +try (Scope ignored = span.makeCurrent()) { + // ... work ... +} catch (Throwable t) { + span.recordException(t); + throw t; +} finally { + span.end(); +} +``` + +In lower-level modules (framework/*, utils/, plugins/), use +`io.opentelemetry.api.GlobalOpenTelemetry.get()` instead of TracingHolder +— it returns the same SDK instance registered by the server at startup, +without requiring a hard dependency on the server module. + +### Async job tracing + +Every async job execution gets its own SERVER span via the +`asyncjob ` span name, with attributes: + +- `cloudstack.job.id` — job ID +- `cloudstack.job.cmd` — command name (e.g. `org.apache.cloudstack.api.command.user.vm.DeployVMCmd`) +- `cloudstack.job.dispatcher` — dispatcher name + +Note: async job spans are currently independent traces (no parent link +to the originating API request). Full end-to-end tracing across the job +queue requires persisting the W3C `traceparent` with the job row — a +follow-up item. + +### Full auto-instrumentation (optional) + +For zero-code instrumentation of JDBC, HTTP clients, Spring, and more, run +the management server with the [OTel Java agent](https://github.com/open-telemetry/opentelemetry-java-instrumentation): + +```bash +java -javaagent:opentelemetry-javaagent.jar \ + -Dotel.service.name=cloudstack-management \ + -Dotel.exporter.otlp.endpoint=http://otel-collector:4318 \ + -jar cloud-client-ui.jar +``` + +The agent and our in-code instrumentation coexist; both contribute spans to +the same trace. + +## Metrics endpoint (Prometheus) + +The management server exposes JVM, process, and HTTP metrics in Prometheus +text format at `GET /metrics`. This complements the existing prometheus +integration plugin (which serves *business* metrics — VMs, hosts, storage — +on its own dedicated port). + +What's exposed: + +- **JVM**: heap usage by region, GC pause times and counts, thread states, + classloader counts, JIT compilation time, heap pressure +- **Process**: uptime, CPU load, file descriptors, system load average +- **HTTP**: request rate and duration histogram per method × route × status class + - `http_server_request_duration_seconds{method, uri, status}` + - `uri` is a coarse route bucket (`/api`, `/console`, `/health`, `/metrics`, + `other`) to bound cardinality + - `status` is a status class (`2xx`, `3xx`, `4xx`, `5xx`) + +### Prometheus scrape config + +```yaml +scrape_configs: + - job_name: cloudstack-management + metrics_path: /client/metrics + static_configs: + - targets: ['mgmt-1:8080', 'mgmt-2:8080'] +``` + +### Add custom metrics + +Anywhere in the server module: + +```java +import com.cloud.servlet.MetricsRegistryHolder; +import io.micrometer.core.instrument.Counter; + +private final Counter myCounter = Counter.builder("cloudstack.my.counter") + .tag("kind", "thing") + .register(MetricsRegistryHolder.get()); + +// ... later +myCounter.increment(); +``` + +## Health check endpoints + +The management server exposes lightweight health endpoints suitable for use +with Kubernetes probes, load balancers, and uptime monitors. + +| Endpoint | Purpose | Behavior | +|----------|---------|----------| +| `GET /health/live` | Liveness | Always returns 200 unless the JVM is wedged. Use for restart triggers. | +| `GET /health/ready` | Readiness | Returns 200 only when the Spring context is initialized and the database is reachable. Use for traffic routing. | +| `GET /health` | Aggregate | Same as `/health/ready`. | + +Responses are plain text (`OK` or a short reason like `database-unreachable`). +The HTTP status code is the source of truth. + +### Kubernetes example + +```yaml +livenessProbe: + httpGet: + path: /health/live + port: 8080 + periodSeconds: 10 +readinessProbe: + httpGet: + path: /health/ready + port: 8080 + periodSeconds: 5 + failureThreshold: 3 +``` + +## Structured logging (JSON) + +CloudStack supports both human-readable text logs (default) and structured +JSON logs for log aggregation pipelines (Loki, Elasticsearch, Datadog, etc.). + +### Enable JSON logs + +Set the environment variable before starting the management server: + +```bash +export CLOUDSTACK_LOG_FORMAT=json +``` + +Output format follows the Elastic Common Schema (ECS), giving you: + +```json +{ + "@timestamp": "2026-05-16T01:23:45.123Z", + "log.level": "INFO", + "log.logger": "com.cloud.vm.UserVmManagerImpl", + "message": "VM lifecycle event", + "process.thread.name": "main", + "service.name": "cloudstack-management" +} +``` + +### Switch back to text + +Unset the variable or set it to anything other than `json`: + +```bash +unset CLOUDSTACK_LOG_FORMAT +# or +export CLOUDSTACK_LOG_FORMAT=text +``` + +### Implementation + +The routing happens in `engine/service/src/main/webapp/WEB-INF/log4j.xml` via +a `` appender that reads the `CLOUDSTACK_LOG_FORMAT` env var. JSON +output uses Log4j2's `JsonTemplateLayout` with the bundled `EcsLayout.json` +template — no extra config needed. + +## Future observability work + +Planned (Phase 5): +- Micrometer-backed metrics endpoint (Prometheus scrape) +- OpenTelemetry tracing for API and orchestration spans +- Health check endpoints +- Trace ID propagation through async jobs diff --git a/docs/PHASE5A_CI.md b/docs/PHASE5A_CI.md new file mode 100644 index 000000000000..f3887f218c28 --- /dev/null +++ b/docs/PHASE5A_CI.md @@ -0,0 +1,163 @@ +# Phase 5a CI: Two-image build (UI + API split) + +This document describes the two new GitHub Actions workflows added as part +of Phase 5a ("UI split") of the CloudStack microservices roadmap, what they +produce, what remains before they are production-ready, and how to reproduce +the same build locally. + +## What the workflows produce + +| Workflow | File | Image | Trigger paths | +|---|---|---|---| +| `Phase 5a – UI image` | `.github/workflows/ui-image.yml` | `ghcr.io//cloudstack-ui` | `ui/**` | +| `Phase 5a – API/management image` | `.github/workflows/api-image.yml` | `ghcr.io//cloudstack-management` | everything except `ui/**` | + +### Image anatomy + +**`cloudstack-ui`** — built from `ui/Dockerfile`: +- Stage 1 (`node:20-bookworm`): `npm install && npm run build` → `ui/dist/` +- Stage 2 (`nginx:alpine`): serves the static SPA; proxies `/client/*` to the + management server via `ui/nginx/default.conf`. + +**`cloudstack-management`** — built from the root `Dockerfile`: +- Stage 1 (`eclipse-temurin:21-jdk-noble`): full Maven build via + `mvn -B -ntp install -DskipTests -P developer,systemvm`. +- Stage 2 (`eclipse-temurin:21-jre-noble`): minimal JRE runtime, `tini` init, + drops to `cloud` (uid 1000) user, exposes 8080 + 8443. +- **UI assets are NOT bundled** into this image — that is the whole point of + Phase 5a. + +### Tagging scheme + +Both workflows use `docker/metadata-action` to produce: +- `-` on every push/PR (e.g. `modernize-2026-abc1234`) +- `` from Git tags when present +- `latest` only on pushes to `main` + +### PR vs merge behaviour + +Each workflow has two distinct blocks: +1. **Pre-merge (PR)** — image is built and layers are cached in the GHA + cache, but `push: false`. No registry credentials are needed or used. +2. **Post-merge (push)** — same build, but `push: true` and the GHCR login + step is enabled. Requires `GHCR_TOKEN` secret (see below). + +This avoids the common failure mode of PRs from forks triggering pushes +without access to registry secrets. + +## What still has to be done before production + +1. **Registry credentials** + - Create a GitHub personal access token (classic) or a fine-grained token + with `write:packages` scope. + - Add it as a repository secret named `GHCR_TOKEN`. + - Replace the placeholder org in `IMAGE_NAME` env vars with your actual + GitHub org/user name (or use `${{ github.repository }}` if your repo is + already at the right path). + +2. **`.dockerignore` for the API build context** + - The root `Dockerfile` currently has no `.dockerignore`. Add one that + excludes `ui/` so the multi-GB `ui/node_modules/` directory is never + sent to the Docker daemon. + - Recommended entries: `ui/node_modules`, `ui/dist`, `.git`, `target/`. + +3. **`ui/Dockerfile` Node upgrade** + - The committed `ui/Dockerfile` uses `node:14-bullseye` (EOL). + - The workflow passes `--build-arg NODE_BASE=node:20-bookworm` as a + workaround, but the Dockerfile does not currently consume that arg. + - TODO: add `ARG NODE_BASE=node:20-bookworm` and change the `FROM` line + to `FROM ${NODE_BASE} AS build`, then drop the build-arg from the workflow. + +4. **Image vulnerability scanning** + - Add `aquasecurity/trivy-action` (or equivalent) as a post-build step in + both workflows. Fail on CRITICAL CVEs. + - Example step to append after the build-push step: + ```yaml + - name: Scan image for CVEs + uses: aquasecurity/trivy-action@0.20.0 + with: + image-ref: ${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} + format: table + exit-code: '1' + severity: CRITICAL + ``` + +5. **SBOM generation** + - Add `anchore/sbom-action` after each push to produce a CycloneDX or + SPDX SBOM and attach it to the GitHub release or as an image attestation. + +6. **Image signing** + - Use `sigstore/cosign-installer` + `cosign sign` to add keyless Sigstore + signatures to every pushed image. Requires `id-token: write` permission + in the job. + +7. **Multi-platform builds** (optional near-term) + - The workflows currently target the runner's native architecture (`amd64`). + - Add `platforms: linux/amd64,linux/arm64` to the build-push step and set + up a QEMU emulation step (`docker/setup-qemu-action`) for `arm64`. + +8. **Helm sub-chart** + - Per the microservices roadmap, Phase 5a also includes a Helm sub-chart + that pairs the two images behind a single nginx-ingress (`/` → UI, + `/client/*` → management). That is tracked separately and not part of + this CI draft. + +## Running the same build locally + +### Prerequisites + +- Docker 24+ with BuildKit enabled (`DOCKER_BUILDKIT=1` or Docker Desktop) +- For the UI: Node 20+ and npm 10+ +- For the API: JDK 21 + Maven 3.9 (or just Docker — the Dockerfile handles + the Maven build in its own stage) + +### UI image + +```bash +# From repo root +docker build \ + --build-arg NODE_BASE=node:20-bookworm \ + -t cloudstack-ui:local \ + ./ui + +# Run locally (serves on http://localhost:8080) +# Point it at a running management server: +docker run --rm -p 8080:80 \ + -e CS_BACKEND_URL=http://localhost:8443 \ + cloudstack-ui:local +``` + +### API / management image + +```bash +# From repo root — this will take 10-20 min on first run (full Maven build) +docker build \ + -t cloudstack-management:local \ + . + +# Run (requires an external MySQL): +docker run --rm -p 8080:8080 -p 8443:8443 \ + -e DB_HOST=127.0.0.1 \ + -e DB_USER=cloud \ + -e DB_PASSWORD=cloud \ + cloudstack-management:local +``` + +### Speed up the API build with a local Maven cache + +```bash +docker build \ + --cache-from type=local,src=/tmp/docker-cache \ + --cache-to type=local,dest=/tmp/docker-cache,mode=max \ + -t cloudstack-management:local \ + . +``` + +## Open questions / TODOs for the team + +- [ ] Confirm desired GHCR org namespace (fork repo vs. separate org). +- [ ] Decide whether to keep the `ui/Dockerfile` Node 14 line or upgrade it + immediately — the workaround build-arg approach is temporary. +- [ ] Agree on the image scanning severity threshold (CRITICAL only vs. HIGH+). +- [ ] Decide signing strategy: keyless Sigstore vs. org-managed key pair. +- [ ] Helm sub-chart ownership: same PR as this CI draft, or a follow-on? diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md new file mode 100644 index 000000000000..f58d6c4dad94 --- /dev/null +++ b/docs/PROGRESS.md @@ -0,0 +1,147 @@ +# Modernization progress — summary + +This fork has 22 commits beyond the upstream Apache CloudStack baseline, +covering everything from dead-code removal through to a production +Kubernetes deployment story. Every commit has tests; the full reactor +runs **10,741 unit tests with 0 failures and 0 errors**. + +## What shipped, by phase + +### Phase 1 — Dead code removal (2 commits) + +- **−143K lines** across 19 dead plugins targeting discontinued products + (BigSwitch, Brocade, Cisco VNMC, CloudByte, Datera, Nicira NVP, + Juniper Contrail, etc.) +- Insecure auth modules removed (MD5, plain-text) +- Cross-references cleaned in API, server, engine, framework, plugins, + and tests + +### Phase 2 — Dependency modernization (1 commit) + +- 30+ dependencies updated +- Security-critical: Bouncy Castle jdk15on 1.70 → jdk18on 1.79, JSch + abandoned → maintained fork, Kafka 2.7 → 3.8, Log4j 2.19 → 2.24, + Jackson 2.13 → 2.18, Guava 31 → 33 +- Maven build plugins all bumped to current major versions +- Test libraries: Hamcrest 1.3 → 2.2, JUnit Jupiter, AssertJ, WireMock + +### Phase 3 — Java 17 + Jakarta + Spring 6; Phase 5 backend baseline to Java 21 + +- Java target: 11 → 17, then fork baseline lifted to 21 +- Spring Framework: 5.3 (EOL) → 6.1 +- Jetty: 9.4 (EOL) → 11.0 +- Tomcat embed → 10.0 (Jakarta) +- Apache CXF: 3.2 (EOL) → 4.0 +- Groovy: 2.4 (EOL) → 4.0 (groupId migrated) +- **1,510 files** migrated from `javax.*` → `jakarta.*` +- Hibernate-ready (JPA 3 / Jakarta Persistence 3.1) +- Significant API breakage fixed: Spring 6 InstantiationAwareBeanPostProcessor, + Jetty 11 websocket API, RequestLog API, SslContextFactory.Server, etc. +- Post-migration test fixes: cglib → Spring-inlined cglib, WireMock 3, + OWASP ESAPI 2.5 (with xalan eviction) + +### Phase 4 — Operability + decomposition + deployment (15 commits) + +#### Operability surface + +| Endpoint | Use | +|----------|-----| +| `GET /health/live` | Kubernetes liveness probe | +| `GET /health/ready` | Kubernetes readiness probe | +| `GET /metrics` | Prometheus scrape (JVM + process + HTTP) | +| W3C trace context propagation | Distributed traces via OTLP | +| JSON structured logs | ECS schema, env-driven (`CLOUDSTACK_LOG_FORMAT=json`) | +| Async job spans | Every job execution traced | + +#### Engineering quality + +- Fork CI workflow with required (build+test) and advisory + (SpotBugs/PMD/OWASP) jobs +- 64 dedicated unit tests for `ConfigurationValueValidator` +- `DEVELOPMENT.md`, `OBSERVABILITY.md`, `REFACTORING.md`, `DEPLOYMENT.md` + +#### God class decomposition — ConfigurationManagerImpl + +8 slices shipped. `ConfigurationManagerImpl.java` 9,509 → 9,286 lines +(−223). Extracted to `ConfigurationValueValidator` (pure utility): + +| Slice | Helpers | +|-------|---------| +| 1 | 10 base validators (`validateValueType`, `validateRange*`, etc.) | +| 2 | `shouldEncryptValue`, `maskEventValueIfEncrypted`, `parseConfigurationTypeIntoString` | +| 3 | `isIpConfigName`, `validateIpConfigValue`, `validateConflictingConfigValue` | +| 4 | `validateCidrList` | +| 5 | 4 validation sets → immutable static constants | +| 6 | VLAN URI parsing → `BroadcastDomainType.parseVlanNumberFromUri` | +| 7 | `validateSpecificConfigurationValues` | +| 8 | Final cleanup | + +Pattern: pure helpers extracted, instance methods kept as delegating +one-liners so Mockito spies and subclass overrides still work. + +#### Container + Kubernetes deployment + +- Multi-stage `Dockerfile` (eclipse-temurin:21, non-root user, tini PID 1, + HEALTHCHECK) +- `docker-compose.yml` for one-command local dev (MySQL 8 + management) +- Helm chart at `deploy/helm/cloudstack-management/` with: + - Liveness/readiness/startup probes targeting Phase 4 endpoints + - Optional ServiceMonitor for Prometheus Operator + - OpenTelemetry env wiring + - ConfigMap-based property overrides + - Ingress template (cert-manager-friendly) + - `helm lint` passes, `helm template` renders cleanly + +## What's not done (recommended follow-ups) + +### Spring-component extraction in god classes + +Pure-helper extraction has exhausted the easy wins. Further god-class +decomposition (`UserVmManagerImpl` at 10K lines, `NetworkServiceImpl`, +etc.) requires moving coherent domain units into their own `@Component` +classes — a heavier per-slice effort needing proper architectural +review, not autonomous batch work. See `docs/REFACTORING.md`. + +### OpenAPI spec generation + +CloudStack uses a custom XML API doc system (`tools/apidoc/`). Adding +OpenAPI 3.x emission from the `BaseCmd` metadata would unlock modern +tooling (Postman, swagger-codegen, mock servers). Estimated effort: +1-2 sessions of focused work introspecting the `@Parameter`/`@APICommand` +annotation graph. + +### Async job W3C traceparent persistence + +Async job spans are currently independent traces (no parent link). +Persisting the `traceparent` header alongside each job row would +enable full end-to-end traces across the queue. Requires schema +migration on `async_job`, plus capture-at-submit / restore-at-execute +plumbing. + +### Removed-during-cleanup plugins that may need replacement + +- **OVM3**: removed. Recommended replacement is the existing KVM plugin + or a new oVirt plugin if needed (per the project decisions). +- **Nicira NVP / Juniper Contrail**: superseded by NSX / Tungsten which + are kept. + +### Phase 5 — new capabilities (untouched) + +The original audit's Phase 5 ideas (container workloads, OVN networking, +eBPF, improved RBAC, API v2) are all open. Each is a substantial +greenfield effort beyond the scope of this modernization pass. + +## Stats + +| | Before | After | +|--|--------|-------| +| Lines of code (total) | ~2,151,000 | ~2,008,000 | +| Dead plugins | 19 | 0 | +| Java version | 11 | 21 | +| Spring | 5.3 (EOL) | 6.1 | +| Jetty | 9.4 (EOL) | 11.0 | +| Java EE namespace | `javax.*` | `jakarta.*` | +| Unit tests | ~10,500 | **10,741** (0 failures) | +| Observability endpoints | 0 | 3 (health, metrics, traces) | +| Deployment story | RPM/DEB | Docker + Helm + RPM/DEB | +| Critical security CVEs | 10+ | 0 (in updated deps) | diff --git a/docs/REFACTORING.md b/docs/REFACTORING.md new file mode 100644 index 000000000000..76e73c8a53e7 --- /dev/null +++ b/docs/REFACTORING.md @@ -0,0 +1,260 @@ +# Refactoring progress and roadmap + +Tracking the incremental decomposition of god classes in the fork. + +## ConfigurationManagerImpl — Pure-helper extraction phase complete + +The "Phase 4 god class slices" series extracted every pure helper that +was safely extractable without changing behavior or breaking the +existing test/spy patterns. See `docs/DEVELOPMENT.md` for the pattern. + +### Slices shipped + +| # | Commit | What was extracted | +|---|--------|--------------------| +| 1 | `98b13ee` | 10 validation helpers (`validateValueType`, `validateRange*`, `shouldValidateConfigRange`, etc.) | +| 2 | `4e89292` | `shouldEncryptValue`, `maskEventValueIfEncrypted`, `parseConfigurationTypeIntoString` | +| 3 | `4d5d955` | `isIpConfigName`, `validateIpConfigValue`, `validateConflictingConfigValue` | +| 4 | `9628dcc` | `validateCidrList` (+ DEVELOPMENT.md) | +| 5 | `86e74b5` | Four validation sets → immutable static constants | +| 6 | `ff3934e` (part 1) | VLAN URI parsing → `BroadcastDomainType.parseVlanNumberFromUri` | +| 7 | `ff3934e` (part 2) | `validateSpecificConfigurationValues` | + +### Where things landed + +- **`ConfigurationValueValidator`** (540 lines, 64 unit tests) — all + pure helpers, no Spring/DAO state. Anyone can call these without + bootstrapping the container. +- **`BroadcastDomainType.parseVlanNumberFromUri`** — VLAN URI parsing + lives with the rest of the URI scheme handling. + +### Size impact + +| State | Lines | Helpers in pure utility | +|-------|-------|-------------------------| +| Before slice 1 | 9,509 | 0 | +| After slice 7 | **9,286** | **64 tests over 16 pure methods + 4 immutable sets** | +| Net change | **−223 lines** | All gain in testability | + +The instance methods on `ConfigurationManagerImpl` are kept as +delegating one-liners so Mockito spies and subclass overrides continue +to work without modification. + +## What's left in ConfigurationManagerImpl (and why pure-helper extraction is exhausted) + +The remaining code is **not pure** — it depends on injected DAOs and +manager beans. Further decomposition requires a different pattern: +**Spring-component extraction** (move a coherent chunk into its own +`@Component` with its own DAO injections). + +Inventory of remaining decomposition targets: + +| Logical unit | ~Lines | Why it can't be pure-extracted | +|--------------|--------|--------------------------------| +| Zone CRUD (`createZone`, `editZone`, `deleteZone`, …) | ~800 | Uses `_zoneDao`, `_clusterDao`, `_alertMgr` | +| Pod CRUD (`createPodIpRange`, `deletePodIpRange`, `updatePodIpRange`, `checkPodAttributes`) | ~600 | Uses `_podDao`, `_privateIpAddressDao` | +| Network/VLAN management (VLAN range CRUD) | ~700 | Uses `_vlanDao`, `_nicDao`, `_networkDao` | +| Service offering CRUD | ~700 | Uses `_serviceOfferingDao`, `_diskOfferingDao` | +| Disk offering CRUD | ~500 | Uses `_diskOfferingDao` | +| `updateConfiguration` orchestration | ~200 | Calls many DAOs, encrypts via Spring beans, fires events | +| `resetConfiguration` orchestration | ~150 | Same | +| `getConfigurationGroupAndSubGroup` | ~100 | DB-backed | + +### Next-pattern playbook (Spring-component extraction) + +1. **Pick a domain unit** — e.g., Pod IP range management. +2. **Create a new `@Component` class** (`PodIpRangeService` or similar) + that takes the DAOs and managers it needs via constructor injection. +3. **Move the methods over** (and any tightly-coupled private helpers). +4. **In `ConfigurationManagerImpl`, replace the moved methods with + delegating calls** through an injected `PodIpRangeService`. +5. **Write integration-style tests** for the new component. +6. **Verify no test or spy in `ConfigurationManagerImplTest` breaks.** + +This is a heavier per-slice effort than pure-helper extraction (Spring +wiring, possible @Transactional boundaries, integration tests rather +than pure unit tests) — appropriate for a longer-lived branch with +proper review. + +## ConfigurationManagerImpl — Spring-component extraction phase + +Having exhausted pure-helper extraction (slices 1–7 above), the next +phase pulls coherent domain units into dedicated `@Component` classes +with their own DAO injections. `ConfigurationManagerImpl` retains +one-line delegating wrappers so the `ConfigurationService` / +`ConfigurationManager` interface contracts are unchanged. + +### Spring-component slices shipped + +| # | Commit | Component | Methods extracted | Dedicated tests | +|---|--------|-----------|-------------------|-----------------| +| 1 | `285223c` | `PodService` | Pod CRUD (`createPod` ×2, `deletePod`) + IP-range ops (`createPodIpRange`, `deletePodIpRange`, `updatePodIpRange`) | 32 | +| 2 | `7acbaa3` | `DiskOfferingService` | Disk-offering CRUD (`createDiskOffering`, `updateDiskOffering`, `deleteDiskOffering`) + associated helpers | 41 | +| 3 | `e57bd08` | `PortableIpRangeService` | `createPortableIpRange`, `deletePortableIpRange`, `listPortableIpRanges`, `listPortableIps` | 22 | +| 4 | `d8799bc` | `ServiceOfferingService` | Service-offering CRUD (`createServiceOffering`, `updateServiceOffering`, `deleteServiceOffering`, `getServiceOfferingDomains`, `getServiceOfferingZones`) | 24 | +| 5 | `5366f7c` | `ZoneService` | Zone CRUD (`createZone`, `editZone`, `deleteZone`, `createDefaultSystemNetworks`) | 22 | + +## UserVmManagerImpl — Spring-component extraction in progress + +`UserVmManagerImpl` is the biggest god class in the codebase. Rather than +pure-helper extraction (which is unsuitable — see below), each slice +pulls a coherent unit of behaviour into its own `@Component` with its +own DAO injections. `UserVmManagerImpl` keeps one-line delegating +wrappers so the `UserVmManager` interface contract and existing test +spies still work. + +### Slices shipped + +| # | Component | Methods extracted | Dedicated tests | +|---|-----------|-------------------|-----------------| +| 1 | `VmGroupService` | 4 instance-group APIs + helpers (assign/unassign/CRUD) | 11 | +| 2 | `ServiceOfferingValidator` | 5 service-offering compatibility checks | 9 | +| 3 | `VmNicService` | 5 NIC APIs + helpers (add/remove/update/default) | 6 | +| 4 | `VmRootDiskValidator` | 4 root-disk validation/sizing methods | 9 | +| 5 | `VmUpdateValidator` | update-VM input validation + service-offering detail merging | 10 | +| 6 | `VmLeaseService` | VM lease validation, create-time apply, update-time apply, detail write | 20 | +| 7 | `VmAssignmentValidator` | assignVMToAccount pre-flight: VM movability, rule absence, snapshot absence, template access, account validity, caller access | 21 | +| 8 | `VmExtraConfigService` | Hypervisor extra-config (KVM/Xen/VMware) decode + allow-list validation + persist | 12 | +| 9 | `VmMigrationValidator` | VM (storage) migration pre-flight: caller/state/snapshot, dest hypervisor/SAGs/tags/dedication/maintenance | 19 | +| 10 | `VmCreationValidator` | createVirtualMachine pre-flight: service-offering / template / details / min-max IOPS | 23 | +| 11 | `VmDestroyPermissionService` | destroy/expunge/force-stop permission cluster (admin + global config + role API access + Kubernetes plugin veto) | 13 | +| 12 | _(skipped)_ | — | — | +| 13 | `VmHostNameUniquenessService` | hostname uniqueness across `vm.distinct.hostname.scope` (global/domain/subdomain/account/network+VPC) + extra-DHCP-option network presence check | 23 | +| 14 | `VmSecurityGroupAssignmentService` | security-group ID resolution (names→IDs, mutex check, VNF-appliance default group injection) + stopped-VM security-group reassignment (`checkAndUpdateSecurityGroupForVM` / `updateSecurityGroup`) | 20 | +| 15 | `VmCredentialResetService` | userdata propagation (`updateUserData`, `applyUserData`), userdata finalization (`finalizeUserData`), password encryption (`encryptAndStorePassword`), SSH-key detail cleanup (`removeEncryptedPasswordFromUserVmVoDetails`) | 21 | +| 16 | `VmUsageEventPublisher` | VM-level usage event publishing (`generateUsageEvent` with dynamic-offering parameter support), per-NIC network-offering events (`generateNetworkUsageForVm`), and the state-aware bulk publish fired on `displayVm` flips (`saveUsageEvent`) | 20 | +| 17 | `VmDisplayFlagService` | `displayVm` flag mutation: set flag on the VO, conditional VM resource-count increment/decrement (suppressed when `resource.count.running.vms.only` is on), bulk usage-event publication via `VmUsageEventPublisher`, and ROOT + DATADISK volume display cascade | 15 | +| 18 | `VmRootVolumeStorageCleanupService` | Hypervisor-aware managed-storage cleanup on VM destroy: XenServer `DetachCommand`, VMware `DeleteCommand` + `ModifyTargetsCommand` cluster broadcast, KVM no-op | 21 | + +> **Note on slice 12:** Slice 12 was claimed and abandoned (duplicate of slice 13 +> domain — VmNetworkHostnameValidator); skipped intentionally. + +Each slice keeps the orchestration that needs spy-verified inner calls +inside `UserVmManagerImpl` — the leaf methods become thin wrappers that +delegate to the extracted component, and the validator's own +implementation can call its helpers directly when invoked standalone. + +## VpcManagerImpl — Spring-component extraction in progress + +Each slice carves a coherent VPC domain unit into its own `@Component`. +`VpcManagerImpl` retains one-line delegating wrappers. + +### Slices shipped + +| # | Commit | Component | Methods extracted | Dedicated tests | +|---|--------|-----------|-------------------|-----------------| +| 1 | `3cbfa35` | `StaticRouteService` | VPC static-route CRUD (`getStaticRoute`, `createStaticRoute`, `listStaticRoutes`, `getVpcStaticRoutes`) + validation + conflict detection + provider application | 27 | +| 2 | `b8b3729` | `PrivateGatewayService` | VPC private-gateway lifecycle (`getVpcPrivateGateways`, `getVpcPrivateGateway`, `getPrivateGatewayProfile`, `createVpcPrivateGateway`, `applyVpcPrivateGateway`, `deleteVpcPrivateGateway`) | 27 | +| 3 | `01c3e51` | `VpcIpAllocationService` | VPC public-IP allocation and release (`allocateIPToVpc`, `releaseIpFromVpc`, and associated helpers) | 23 | + +> The commit message for `VpcIpAllocationService` labels it "slice 4" because +> it was the fourth parallel extraction in the worktree series; the table above +> uses sequential semantic ordering (StaticRoute → PrivateGateway → IpAllocation). + +## VirtualMachineManagerImpl (engine) — Spring-component extraction in progress + +Engine-layer god class. Each slice extracts one infrastructure concern +into a dedicated `@Component` under `engine/orchestration`. + +### Slices shipped + +| # | Commit | Component | Methods extracted | Dedicated tests | +|---|--------|-----------|-------------------|-----------------| +| 1 | `b6b6b9f` | `VmServiceOfferingUpgradeManager` | Service-offering upgrade persistence helpers: volume / primary-store / instance-details rewrite on scale-up | 18 | +| 2 | `f23af88` | `VmIscsiTargetManager` | VMware managed-iSCSI dynamic-target cleanup trio (`getTargets`, `removeDynamicTargets`, `sendModifyTargetsCommand`) | 16 | +| 3 | `c212a28` | `VmStatsCollector` | Per-host VM / disk / network stats collection and aggregation | 29 | + +## ManagementServerImpl — Spring-component extraction in progress + +Each slice extracts one management-surface concern from the +`ManagementServerImpl` god class into a dedicated `@Component`. + +### Slices shipped + +| # | Commit | Component | Methods extracted | Dedicated tests | +|---|--------|-----------|-------------------|-----------------| +| 1 | `0c002f8` | `SshKeyPairService` | SSH keypair generation, registration, listing, deletion | 18 | +| 2 | `6206c91` | `AuditTrailService` | Audit-trail and alert lifecycle: `archiveEvents`, `deleteEvents`, `searchForAlerts`, `archiveAlerts`, `deleteAlerts`, `listEventTypes` | 15 | +| 3 | `91ffe0e` | `HypervisorCapabilitiesService` | Hypervisor-capabilities catalogue read/write: `listHypervisorCapabilities`, `updateHypervisorCapabilities`, `getHypervisorCapabilitiesForUpdate` | 18 | +| 4 | `468d7cc` | `HostCredentialsService` | Host/cluster credential management: `getVMPassword`, `getSSHPublicKeys`, `getCloudIdentifier` | 20 | +| 5 | `7555c01` | `ConsoleAccessService` | Console-proxy and VNC lookups: `getConsoleAccessUrlRoot`, `setConsoleAccessForVm`, `getConsoleAccessAddress`, `getVncPort` | 20 | +| 6 | `0005daf` | `SystemVmLifecycleService` | Type-aware system-VM start/stop/reboot/destroy dispatch + `findSystemVMTypeById` | 22 | +| 7 | `b6d0250` | `CapabilitiesService` | Deployment introspection: `listCapabilities` (with `getVpnCustomerGatewayParameters` helper) and `getVersion` | 20 | +| 8 | `c8b9ab1` | `ConfigurationListingService` | `searchForConfigurations` (scope validation, domain-admin/user defaulting, keyword/group/category filters, ConfigDepot re-population) and `listConfigurationGroups` | 17 | + +## AccountManagerImpl — Spring-component extraction in progress + +Each slice extracts one account-management concern from +`AccountManagerImpl` into a dedicated `@Component`. + +### Slices shipped + +| # | Commit | Component | Methods extracted | Dedicated tests | +|---|--------|-----------|-------------------|-----------------| +| 1 | `245e543` | `AccountLookupService` | Read-only account/user lookup cluster: `getActiveAccountByName`, `getActiveUserAccount`, and related `findByX` accessors | 19 | +| 2 | `e90ec28` | `ApiKeyPermissionService` | API-key permission / superset-check cluster: caller API-key parsing + rule-set resolution | 17 | +| 3 | `452a0db` | `ApiKeyLifecycleService` | API-key generation, persistence, and removal helpers | 18 | +| 4 | `2e5a21d` | `TwoFactorAuthenticationService` | User 2FA provider-registry lookups + login-time setup-state cleanup | 18 | +| 5 | `dec0092` | `AclSearchBuilderService` | ACL search-builder/criteria/parameters cluster: `buildACLSearchBuilder`, `buildACLSearchCriteria`, `buildACLSearchParameters`, `buildACLViewSearchBuilder`, `buildACLViewSearchCriteria` | 37 | + +## Other god classes in the codebase + +These are the remaining `*ManagerImpl` classes over 5K lines, by size. +Classes already receiving Phase 4 slices are noted. + +| File | Lines (approx.) | Notes | +|------|-----------------|-------| +| `server/.../UserVmManagerImpl.java` | 10,068 | **18 slices shipped** (see above). | +| `server/.../ConfigurationManagerImpl.java` | 9,286 | Pure-helper extraction complete (7 slices) + **5 Spring-component slices shipped** (see above). | +| `server/.../NetworkServiceImpl.java` | 6,484 | Network CRUD, VPC management. Parallel slice extraction started. | +| `server/.../QueryManagerImpl.java` | 6,372 | API list query handlers. Parallel slice extraction started. | +| `server/.../ManagementServerImpl.java` | 5,994 | **8 slices shipped** (see above). | +| `server/.../ApiResponseHelper.java` | 5,878 | Response serialization (likely many static-extractable helpers). | +| `server/.../VolumeApiServiceImpl.java` | 5,513 | Volume lifecycle. Parallel slice extraction started. | +| `server/.../VpcManagerImpl.java` | ~5,200 | **4 slices shipped** (see above). | +| `server/.../AccountManagerImpl.java` | ~5,000 | **5 slices shipped** (see above). | +| `engine/.../VirtualMachineManagerImpl.java` | ~4,800 | **3 slices shipped** (see above). | + +### Pure-helper extraction is the wrong tool for most remaining god classes + +After surveying `ApiResponseHelper.java` (5,877 lines, 195 methods): +its few `public static` helpers (`getPrettyDomainPath`, +`setResponseIpAddress`, `populateOwner`, etc.) are already extracted +in place — they're callable directly without instantiating the class. +The remaining ~5,800 lines are instance methods that interleave DAO +lookups with response-DTO construction. Extracting the pure +post-lookup computation parts would yield 5-10 line slices for each +of 195 methods — high churn, low semantic value. + +The same applies to `UserVmManagerImpl`, `NetworkServiceImpl`, +`QueryManagerImpl`, etc. These are Spring components where the +domain logic is genuinely entangled with infrastructure calls. + +## Recommended next decomposition pattern + +For the remaining god classes, **Spring-component extraction** is the +right pattern, not pure-helper extraction: + +1. Pick a coherent domain unit (e.g., "VM clone creation", + "Network ACL rule management"). +2. Create a `@Component` that owns just those DAOs and beans, with + constructor injection. +3. Move the related methods over (incl. their helpers). +4. Replace call sites in the original god class with delegating calls + through an injected reference to the new component. +5. Spy/mock at the new boundary in tests. + +This produces larger, less-frequent slices than the pure-helper +pattern — each one is a meaningful architectural change deserving +proper review. It's not appropriate for autonomous batch execution +in the same way pure-helper slices were. + +## Suggested high-impact next targets + +| Target | Why | +|--------|-----| +| Add **OpenAPI spec generation** | Modernizes the API surface; very high external value | +| **`UserVmManagerImpl` clone/migration extraction** | Natural seam in the biggest god class | +| **Dockerfile + Helm chart** | Leverages the observability endpoints already added in Phase 4 | +| **Async-job trace propagation** | Carry traceparent through the job queue so VM operations stay traceable end-to-end | +| **Per-plugin SPI improvements** | Direct support for the user's "extend the platform over time" goal | diff --git a/docs/phase5b-local-dev.md b/docs/phase5b-local-dev.md new file mode 100644 index 000000000000..8776b165e6d8 --- /dev/null +++ b/docs/phase5b-local-dev.md @@ -0,0 +1,88 @@ +# Phase 5b Local Auth and BFF Development + +This stack adds the local services needed by the Phase 5b Auth.js/BFF work without wiring application code in this slice. + +## Services + +- `redis`: Redis 7 Alpine, enabled by default for BFF session storage. +- `authentik-postgres`: Authentik's local PostgreSQL database, enabled by the `auth` profile. +- `authentik-server`: local Authentik UI and OIDC issuer, enabled by the `auth` profile. +- `authentik-worker`: Authentik background worker, enabled by the `auth` profile. + +## Run the Stack + +Start the existing CloudStack/MySQL/web stack plus Redis: + +```bash +docker compose up --build +``` + +Start the local Authentik services as well: + +```bash +docker compose --profile auth up --build +``` + +URLs: + +- CloudStack legacy UI: `http://localhost:8080/client` +- Phase 5 web UI: `http://localhost:3000` +- Authentik local dev UI: `http://authentik.localhost:9000` +- Redis from the host: `redis://localhost:6379` +- Redis from compose services: `redis://redis:6379` + +## Web Environment + +For host-based Next.js development, copy `web/.env.example` to `web/.env.local` and fill the Phase 5b values when the Auth.js/BFF code lands: + +```dotenv +NEXTAUTH_URL=http://localhost:3000 +NEXTAUTH_SECRET=replace-me-with-output-of-openssl-rand-hex-32 +REDIS_URL=redis://localhost:6379 +AUTHENTIK_ISSUER=http://localhost:9000/application/o/cloudstack/ +AUTHENTIK_CLIENT_ID=cloudstack-bff +AUTHENTIK_CLIENT_SECRET=replace-me-with-authentik-provider-secret +CS_URL=http://localhost:8080 +CS_SERVICE_APIKEY=replace-me-with-local-service-account-api-key +CS_SERVICE_SECRETKEY=replace-me-with-local-service-account-secret-key +BFF_SESSION_TTL_SECONDS=28800 +CS_SESSION_REFRESH_MARGIN_SECONDS=120 +``` + +Inside `docker compose`, the `web` service already receives container-network defaults: + +- `REDIS_URL=redis://redis:6379` +- `CS_URL=http://cloudstack:8080` +- `AUTHENTIK_ISSUER=http://authentik.localhost:9000/application/o/cloudstack/` + +The compose Authentik issuer uses `authentik.localhost` so browser redirects resolve to the host port while the `web` container resolves the same name to the Authentik service through a compose network alias. If you run Next.js directly on the host, `http://localhost:9000/application/o/cloudstack/` is also fine as long as the Authentik provider/application is created with the same issuer host. + +## Manual Authentik Setup + +Compose starts Authentik, but the local OIDC application/provider still needs to be created in the Authentik UI. + +1. Open `http://authentik.localhost:9000`. +2. Complete Authentik's first-run admin setup if the local volume is empty. +3. Create an OAuth2/OpenID provider: + - Name: `CloudStack BFF` + - Client ID: `cloudstack-bff` + - Client secret: copy into `AUTHENTIK_CLIENT_SECRET` + - Redirect URI: `http://localhost:3000/api/auth/callback/authentik` + - Signing key: Authentik's default generated signing key is fine for local dev. +4. Create an application: + - Name: `CloudStack` + - Slug: `cloudstack` + - Provider: `CloudStack BFF` +5. Confirm the issuer URL is `http://authentik.localhost:9000/application/o/cloudstack/` for compose-based web development, or `http://localhost:9000/application/o/cloudstack/` for host-based Next.js development. + +Service-account API credentials for `CS_SERVICE_APIKEY` and `CS_SERVICE_SECRETKEY` remain a CloudStack-side manual step until the Java/API slice lands. + +## Local Secrets + +The compose file includes insecure local defaults so `docker compose config` and first boot are easy. Do not reuse these defaults outside local development: + +- `NEXTAUTH_SECRET` +- `AUTHENTIK_SECRET_KEY` +- `AUTHENTIK_POSTGRES_PASSWORD` +- `AUTHENTIK_CLIENT_SECRET` +- CloudStack service API keys diff --git a/docs/superpowers/plans/2026-05-21-phase5-next-sessions.md b/docs/superpowers/plans/2026-05-21-phase5-next-sessions.md new file mode 100644 index 000000000000..d8c25316cadf --- /dev/null +++ b/docs/superpowers/plans/2026-05-21-phase5-next-sessions.md @@ -0,0 +1,306 @@ +# Phase 5 Next Sessions Plan + +## Current Position + +Phase 5 has crossed from scaffold into a real CloudStack operator surface. The +Next.js app now has authenticated BFF plumbing, real dashboard and inventory +pages, major detail routes, a guarded Deploy Wizard, and mutation controls for +instances, volumes, SSH keys, templates, security groups, and network public IPs. + +The next sessions should turn that breadth into a shippable foundation: browser +coverage for the new mutation surfaces, real console access for instance detail, +and Phase 5e polish for accessibility, internationalisation, and loading states. + +## Strategic Goal + +Make the Phase 5 UI safe to extend at speed. + +That means each new CloudStack surface should have: + +- Unit coverage for CloudStack response normalisation and BFF behavior. +- Browser smoke coverage against mocked BFF responses. +- Keyboard and screen-reader sane interactions for dialogs, tabs, menus, and + destructive actions. +- A clear i18n path that avoids scattering new hard-coded operator copy through + the app. +- Clean session handoff state: merged branches, pushed `modernize-2026`, updated + handover, and no abandoned worktrees. + +## Session 1: Browser Smoke Harness + +### Why First + +The repo has strong Node test coverage for the BFF and CloudStack data helpers, +but the new action surfaces are mostly unprotected at the browser level. Before +adding more UI, add a mocked-BFF e2e harness so future slices can prove that the +operator workflows still open, validate, submit, and render status correctly. + +### Primary Deliverables + +- Add Playwright test infrastructure under `web/`. +- Add a local mocked BFF fixture layer for `/api/cs/*`. +- Add smoke tests for: + - Deploy Wizard catalog load, advanced option entry, guarded launch submit, and + async job polling display. + - Instance list action buttons and instance detail tabs. + - Volume detach/delete flows. + - SSH key create/register/delete flows. + - Template delete/copy/featured flows. + - Security group create/delete/rule flows. + - Network detail public IP acquire/release/static NAT flows. +- Add `npm run test:e2e` and focused documentation for running the suite. + +### Suggested Branches + +- `phase5e-smoke-harness` +- `phase5e-deploy-wizard-smoke` +- `phase5e-action-pages-smoke` +- `phase5e-network-security-smoke` + +### Suggested Agent Split + +- Worker A owns Playwright config, scripts, fixture helpers, and README updates. +- Worker B owns Deploy Wizard specs and launch/job-polling mocks. +- Worker C owns instance, volume, SSH key, and template specs. +- Worker D owns network and security group specs. +- Coordinator owns integration, naming consistency, and final full verification. + +### Acceptance Criteria + +- `npm run test:e2e` runs locally from `web/`. +- Tests do not require a live CloudStack server. +- Mock fixtures are obvious enough that later slices can add commands quickly. +- Existing checks still pass: + - `node --test --experimental-strip-types app/api/cs/route-core.test.ts lib/bff/*.test.ts lib/cloudstack/*.test.ts` + - `npm run typecheck` + - `npm run lint` + - `npm run build` + +## Session 2: Instance Console Access + +### Why Next + +Instance detail is now tabbed, which gives a natural home for console access. +This is one of the highest-value operator actions still missing, but it needs a +careful CloudStack API scout first because console URLs and session material +should stay short-lived and server-mediated. + +### Primary Deliverables + +- Scout the current CloudStack API command and legacy UI implementation for + console access. +- Add a typed CloudStack helper for console access response normalisation. +- Add route-core or BFF tests for the command shape. +- Add an Instance Detail Console tab or action panel. +- Keep console launch user-initiated and avoid persisting console URLs in client + state longer than needed. +- Render clear unavailable/error states when the instance is stopped, destroyed, + or the API refuses console access. + +### Suggested Branches + +- `phase5d-console-access-scout` +- `phase5d-console-access-bff` +- `phase5d-console-access-ui` + +### Suggested Agent Split + +- Worker A scouts API command names, parameters, response shape, and legacy UI + behavior. +- Worker B implements helper and unit tests. +- Worker C implements UI wiring in instance detail. +- Worker D does a focused security review of URL handling and client exposure. + +### Acceptance Criteria + +- Console access command is verified from repo source or CloudStack API docs in + the checked-out tree before implementation. +- No raw CloudStack secret, API key, session key, or durable console URL is + exposed unnecessarily. +- Instance detail still builds and existing instance-detail tests pass. +- Browser smoke coverage includes the console tab/button with mocked responses. + +## Session 3: Accessibility And Keyboard Hardening + +### Why Here + +The new UI uses Radix primitives and has a strong base, but the action-heavy +surfaces need explicit keyboard and screen-reader verification before they grow +further. + +### Primary Deliverables + +- Add `@axe-core/playwright` once Playwright is available. +- Add smoke-level axe checks for core pages: + - Dashboard + - Instances + - Instance Detail + - Deploy Wizard + - Networks + - Security Groups + - Settings +- Harden Deploy Wizard keyboard behavior: + - Predictable focus after opening and closing. + - Keyboard path through required fields. + - Clear disabled and busy states. + - `aria-live` status for launch and job polling. +- Harden destructive action dialogs and dropdown actions: + - Focus returns to the invoking control. + - Destructive buttons have explicit accessible names. + - Async success and failure states are announced. + +### Suggested Branches + +- `phase5e-axe-smoke` +- `phase5e-deploy-keyboard` +- `phase5e-action-a11y` + +### Suggested Agent Split + +- Worker A owns axe setup and baseline specs. +- Worker B owns Deploy Wizard keyboard/focus fixes. +- Worker C owns action components accessibility fixes. +- Coordinator owns review to avoid duplicating utility patterns. + +### Acceptance Criteria + +- Axe smoke tests pass for selected pages with mocked BFF data. +- Keyboard-only flow can open Deploy Wizard, complete the required launch path, + and close or submit without pointer input. +- Async action results are announced without visual-only feedback. +- No broad styling churn outside the touched components. + +## Session 4: i18n Foundation + +### Why After Smoke Coverage + +`next-intl` is part of the Phase 5 plan, but i18n can sprawl quickly. Once +browser smoke tests are in place, the safest first slice is to wire the provider +and migrate stable shell/navigation copy before moving page-level operator text. + +### Primary Deliverables + +- Add `next-intl` app wiring if not already present. +- Add `web/messages/en.json`. +- Add a small translation helper pattern for server and client components. +- Migrate shell/navigation/page-header copy first: + - Sidebar labels + - Topbar scope/user labels where static + - Settings section labels + - Shared action status labels where low risk +- Document the migration pattern so future slices do not invent competing + message shapes. + +### Suggested Branches + +- `phase5e-i18n-foundation` +- `phase5e-shell-messages` +- `phase5e-action-messages` + +### Suggested Agent Split + +- Worker A owns provider/config/messages. +- Worker B owns shell and navigation message extraction. +- Worker C owns shared action component messages. +- Coordinator keeps the message namespace coherent. + +### Acceptance Criteria + +- The app still renders with the default English locale. +- No route behavior changes. +- Typecheck and build pass. +- The message file structure is documented and easy for later agents to follow. + +## Session 5: Loading And Empty-State Polish + +### Why Last In This Block + +The real data surfaces are now broad enough that perceived reliability matters. +This slice improves operator confidence without changing CloudStack behavior. + +### Primary Deliverables + +- Add route-level or component-level loading states for slow pages. +- Standardise empty/error states for major inventory pages. +- Reuse existing `Skeleton`, `Badge`, `Card`, and table primitives. +- Avoid decorative redesign; keep it operational and dense. + +### Suggested Branches + +- `phase5e-loading-routes` +- `phase5e-empty-states` + +### Acceptance Criteria + +- Loading states are stable and do not cause layout jumps. +- Empty states are useful but not marketing-like. +- Build, lint, and relevant browser smoke tests pass. + +## Coordination Rules + +- Use isolated worktrees for every implementation branch. +- Keep worker ownership disjoint where possible. +- Merge only after targeted verification in the worker branch. +- Run full web verification from `modernize-2026` after each merge batch. +- Push `modernize-2026` after a green batch. +- Update `/Users/damian/Claude/HANDOVER.md` before any usage-window wrap-up. +- Stop launching new workers once usage drops under roughly 20%; finish, merge, + verify, push, and hand over. + +## Recommended First Batch + +Start with four workers plus coordinator integration: + +1. `phase5e-smoke-harness`: Playwright config, scripts, base fixture helper. +2. `phase5e-deploy-wizard-smoke`: Deploy Wizard smoke specs against mocked BFF. +3. `phase5e-action-pages-smoke`: Instances, Volumes, SSH Keys, Templates specs. +4. `phase5d-console-access-scout`: console API command and security scout only. + +When Worker 1 lands the harness, rebase the smoke-spec branches onto it or merge +the harness first, then adapt the specs. If the console scout returns quickly and +the command shape is clear, dispatch BFF and UI workers for console access while +the smoke tests continue. + +## Verification Gate For Each Big Batch + +Run from `web/`: + +```bash +node --test --experimental-strip-types app/api/cs/route-core.test.ts lib/bff/*.test.ts lib/cloudstack/*.test.ts +npm run typecheck +npm run lint +npm run build +npm run test:e2e +``` + +Run from repo root: + +```bash +git status -sb +git diff --check +git log --oneline -12 +``` + +## Risks To Manage + +- Playwright dependency install or browser install may need network access and + enough time in the usage window. +- Mocked BFF tests can become false confidence if they drift from route-core + behavior; keep helper fixtures close to actual response shapes. +- Console access can leak sensitive or long-lived URLs if implemented casually; + scout first and review URL handling before merge. +- i18n can become a wide churn branch; keep the first slice to foundation and + shared shell text. +- Accessibility fixes should reuse existing primitives and avoid redesigning + page layouts as part of the same branch. + +## Done For This Phase Block + +This block is done when: + +- Mutation surfaces have browser smoke coverage. +- Instance console access is implemented or explicitly deferred with scout notes. +- Core pages have baseline axe coverage. +- The i18n foundation is present and documented. +- Loading and empty states are consistent across the main inventory pages. +- `modernize-2026` is green, pushed, and reflected in `HANDOVER.md`. diff --git a/docs/superpowers/plans/2026-05-21-phase5d-console-access-scout.md b/docs/superpowers/plans/2026-05-21-phase5d-console-access-scout.md new file mode 100644 index 000000000000..0aa9f1f57bb8 --- /dev/null +++ b/docs/superpowers/plans/2026-05-21-phase5d-console-access-scout.md @@ -0,0 +1,194 @@ +# Phase 5d Console Access Scout + +## Scope + +This is an investigation note for wiring instance console access into the Phase 5 +Next.js UI. It intentionally does not implement the BFF or UI behavior. + +## Findings + +### Command + +The user-facing command is `createConsoleEndpoint`. + +- API registration and authorization live in + `api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java:43`. +- The command has been available since CloudStack `4.18.0` and is authorized for + `Admin`, `ResourceAdmin`, `DomainAdmin`, and `User` roles. +- `ManagementServerImpl` registers `CreateConsoleEndpointCmd` and + `ListConsoleSessionsCmd` in the API command list. +- The legacy Vue UI checks whether `createConsoleEndpoint` is present in the + user's API list before showing the console button. +- The only integration smoke coverage found is + `test/integration/smoke/test_console_endpoint.py:test_console_endpoint_permissions`. + +There is also a related admin/observability command, `listConsoleSessions`, but +that is not needed for launching an instance console in Phase 5. + +### Parameters + +`createConsoleEndpoint` accepts: + +- `virtualmachineid` - required UUID of the instance. +- `token` - optional extra security token, used only when extra validation is + enabled. +- `client-inet-address` - not declared as a normal `@Parameter`; the command + reads it from the full URL parameter map via `ConsoleAccessUtils.CLIENT_INET_ADDRESS_KEY`. + This becomes the console endpoint creator/source address recorded in session + state. The current Phase 5 generic BFF can pass this key because + `web/lib/cloudstack/request.ts` allows hyphenated parameter names. + +The Phase 5 UI should normally send only `virtualmachineid`. Do not invent a +client-side `token`; the existing system only uses it when some caller has a +specific extra-validation flow. + +### Response Shape + +The command returns a synchronous `createconsoleendpointresponse` containing a +`consoleendpoint` object. Important fields: + +- `success` - boolean result. +- `url` - launch URL when `success` is true; `null` on permission failures. +- `details` - human-readable failure reason. +- `websocket` - optional object with `host`, `port`, `path`, `token`, and `extra`. + +The response does not return an async job id. It creates or denies the endpoint +immediately. Existing smoke coverage asserts a permitted user receives +`success == true` and non-empty `url`, while a different account receives +`success == false` and no URL. + +### Server Behavior + +`ConsoleAccessManagerImpl.generateConsoleEndpoint` performs the meaningful +server-side checks: + +- Refuses when console services or ticket hash keys are not ready. +- Finds the VM and validates the calling account against it. +- Denies router, console proxy, secondary storage VM, unknown VM types, LXC, Edge + zones, unsupported VM states, missing host, and missing console proxy URL root. +- Generates a fresh session UUID per call. +- Persists a console session row. +- For proxied VNC/noVNC, encrypts a `ConsoleProxyClientParam` into the console + token and calls `setConsoleAccessForVm`, making the session one-use on the + console proxy side. +- For external hypervisors/direct mode, returns the direct URL and does not call + `setConsoleAccessForVm`. + +The generated URL can include: + +- `/resource/noVNC/vnc.html?autoconnect=true&show_dot=...&port=...&token=...` +- `/ajax?token=...` for Hyper-V or when noVNC is not the default. +- `&extra=...` when an extra security token is supplied. +- `&guest=windows` for Windows guest OS category. + +That URL contains sensitive, short-lived session material. Treat it as a secret +for logging, persistence, browser history, analytics, and client state. + +### Legacy UI Behavior + +Legacy console access is implemented in `ui/src/components/widgets/Console.vue`. + +Behavior to preserve: + +- Show only when the route is a VM-like route, `listVirtualMachines` exists, and + `createConsoleEndpoint` exists. +- Disable for `Stopped`, `Restoring`, `Error`, `Destroyed`, and + `hostcontrolstate === 'Offline'`. +- Launch is user-initiated. +- Support both "open console" and "copy console URL" actions. +- If `resource.details['External:console_url']` exists, use that direct URL + without calling `createConsoleEndpoint`. +- Otherwise call `postAPI('createConsoleEndpoint', { virtualmachineid: resource.id })`. +- Open the returned URL in a new tab only when `success` is true; otherwise show + `details`. + +Behavior to avoid or improve: + +- Do not keep the returned URL in long-lived React state or server-rendered HTML. +- Do not log the URL, websocket token, or extra value. +- Do not prefetch or generate console URLs during page render. A call appears to + persist session state and should stay click-triggered. +- Avoid showing/copying the URL from a passive server component. +- Consider `rel="noopener noreferrer"` behavior for any direct anchor/open flow. + +## Recommended Phase 5 Design + +Keep this as a tiny helper plus client-side button flow; the existing generic +`/api/cs/[command]` BFF can already proxy the command safely if the helper sends +only safe JSON. + +Suggested implementation slice: + +1. Add `web/lib/cloudstack/instance-console.ts`. + - Export `createConsoleEndpoint(virtualMachineId, options?)`. + - POST to `/api/cs/createConsoleEndpoint` with + `{ virtualmachineid: virtualMachineId }`. + - Normalize the response to: + `{ success: true, url, websocket? }` or + `{ success: false, details }`. + - Reject malformed successful responses with missing/empty `url`. + - Preserve `websocket` fields in the type for later noVNC embedding work, but + do not use them in the first UI slice. + +2. Add `web/lib/cloudstack/instance-console.test.ts`. + - `createConsoleEndpoint posts virtualmachineid only to the BFF` + - `createConsoleEndpoint normalizes successful URL responses` + - `createConsoleEndpoint returns failure details without a URL` + - `createConsoleEndpoint rejects malformed successful responses` + - `createConsoleEndpoint does not pass sessionkey command or response` + +3. Add a small client component, likely + `web/components/instances/instance-console.tsx`. + - Props: `id`, `name`, `state`, optional `hostControlState`, optional + `externalConsoleUrl`. + - Disable for stopped/restoring/error/destroyed/offline states. + - On click, if `externalConsoleUrl` exists, `window.open` it. + - Otherwise call the helper and open the returned URL in a new tab. + - Keep status text minimal: idle, launching, unavailable/error. + - Do not render the URL into the DOM. + +4. Wire into `web/app/(app)/instances/[id]/page.tsx`. + - Replace the current placeholder `ConsolePanel` body with the client + component. + - Pass only detail fields needed by the client component. + - Consider extending `InstanceDetail` to expose + `details['External:console_url']` from `listVirtualMachines` if the API + response includes VM details in Phase 5. + +5. Optional later slice: if Phase 5 wants embedded noVNC instead of opening the + CloudStack console page, use the `websocket` response fields. That should be a + separate security/design pass because it moves more console session material + into the Next.js UI. + +## Risks And Notes + +- `createConsoleEndpoint` is synchronous, but it has side effects: session + persistence and one-use session registration. Avoid automatic calls. +- Returned `url`, `websocket.token`, and `websocket.extra` are sensitive. +- The existing API annotation says `responseHasSensitiveInfo = false`, but the + generated URL contains a console token. Phase 5 should treat it as sensitive + anyway. +- The generic BFF currently proxies arbitrary valid command names. That is + acceptable for this small slice, but console launch should still go through a + typed helper so UI code cannot accidentally pass extra parameters. +- Legacy direct external console URLs bypass `createConsoleEndpoint`. Preserve + the behavior only for URLs already present in VM details; do not let the client + submit arbitrary direct console URLs. +- Permission and state checks belong to CloudStack. The Next.js UI should only + mirror obvious disabled states for ergonomics, not as the security boundary. + +## Source References + +- `api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java:43-110` +- `api/src/main/java/org/apache/cloudstack/api/response/CreateConsoleEndpointResponse.java:29-43` +- `api/src/main/java/org/apache/cloudstack/api/response/ConsoleEndpointWebsocketResponse.java:29-47` +- `server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java:276-320` +- `server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java:346-427` +- `server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java:513-547` +- `server/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManagerImpl.java:564-632` +- `services/console-proxy/server/src/main/java/com/cloud/consoleproxy/ConsoleProxy.java:190-220` +- `ui/src/components/widgets/Console.vue:20-100` +- `test/integration/smoke/test_console_endpoint.py:99-122` +- `web/app/(app)/instances/[id]/page.tsx:53-145` +- `web/lib/cloudstack/request.ts` +- `web/app/api/cs/[command]/route-core.ts` diff --git a/docs/superpowers/plans/2026-05-21-phase5j-settings-surfaces.md b/docs/superpowers/plans/2026-05-21-phase5j-settings-surfaces.md new file mode 100644 index 000000000000..f5d72ef58bb1 --- /dev/null +++ b/docs/superpowers/plans/2026-05-21-phase5j-settings-surfaces.md @@ -0,0 +1,1899 @@ +# Phase 5j Settings Surfaces Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Turn the Phase 5 settings placeholders into practical CloudStack-backed profile, security, API-token, and settings navigation surfaces, with browser coverage and a small isolated cleanup for the Node module warning. + +**Architecture:** Keep Phase 5j inside the existing Next.js/BFF pattern: server pages call typed helpers under `web/lib/cloudstack`, helpers fetch same-origin `/api/cs/*` routes and preserve mock fallback when `CS_URL` is absent, and client actions POST only safe command parameters. Split by settings page so subagents can work in isolated branches and the coordinator can resolve the shared `web/messages/en.json` merge centrally. + +**Tech Stack:** Next.js 14 App Router, React 18, next-intl, Node `node:test`, Playwright, CloudStack BFF route `/api/cs/[command]`, Java 17 remains the repo verification target outside `web/`. + +--- + +## Current Checkpoint + +- Current branch: `modernize-2026` +- Initial pushed plan checkpoint: `42d213d8f2` +- Phase 5j implementation base: `436457a38521bab74f235841da1d38e7d0e38c0f` +- `42d213d8f2` is a doc-only commit on top of the Phase 5i code checkpoint. +- Phase 5i completed: + - Remaining high-traffic page i18n. + - Action failure e2e coverage. + - Detail not-found e2e coverage. + - E2E coverage README updates. +- Phase 5j was briefly started and then intentionally paused at user request. +- These empty Phase 5j worktrees currently exist at the implementation base commit and can be reused after verifying they remain clean: + - `.worktrees/phase5j-profile-settings` + - `.worktrees/phase5j-api-token-settings` + - `.worktrees/phase5j-security-settings` + - `.worktrees/phase5j-settings-index` + - `.worktrees/phase5j-settings-e2e` + - `.worktrees/phase5j-package-metadata` +- No Phase 5j worker produced a commit before shutdown. + +## Scope + +Phase 5j is a focused settings block. It should not expand into a full account-admin console. + +In scope: + +- `/settings` landing page. +- `/settings/profile` read-only current-user profile surface. +- `/settings/security` read-only current-user security status, with password-change action only if it stays small. +- `/settings/api-tokens` API-key visibility and generation surface. +- Settings e2e coverage aligned with the new surfaces. +- Optional package metadata cleanup for `MODULE_TYPELESS_PACKAGE_JSON` warnings. + +Out of scope: + +- Admin CRUD for all users. +- Editing account/domain/project settings. +- Full notification, integration, billing, or advanced settings implementations. +- Broad design-system refactors. +- Any Veeam/KVM work. +- Any renewed god-class decomposition work. + +## Coordination Rules + +- Use isolated worktrees and branches for implementation. +- Keep worker ownership disjoint except for `web/messages/en.json`; the coordinator owns final message-file merge. +- Workers should run targeted verification in their branch before committing. +- Coordinator should merge one slice at a time into `modernize-2026`, resolving `web/messages/en.json` by preserving all `Settings.pages.*` subtrees. +- After the batch, run full web verification from `modernize-2026`: + +```bash +cd web +npm run test:unit +npm run typecheck +npm run lint +npm run build +PLAYWRIGHT_PORT=3155 npm run test:e2e +``` + +- Run from repo root: + +```bash +git diff --check +git status -sb +git log --oneline -12 +``` + +- Push only after green verification: + +```bash +git push origin modernize-2026 +``` + +- Update `/Users/damian/Claude/HANDOVER.md` after merge/push. + +## File Map + +Create or modify these files. + +- `web/lib/cloudstack/users.ts` + - Owns current-user CloudStack `listUsers` mapping and mock fallback. + - Exports `CloudStackUserProfile`, `getCurrentUserProfileFromBff`, `mapCloudStackUserToProfile`, and `buildListUsersUrl`. + +- `web/lib/cloudstack/users.test.ts` + - Tests current-user mapping, BFF fetch shape, missing envelope fallback, HTTP failure fallback, and `CS_URL` absent fallback. + +- `web/app/(app)/settings/profile/page.tsx` + - Replaces placeholder empty state with profile cards/rows. + +- `web/lib/cloudstack/api-tokens.ts` + - Owns `getUserKeys`, `registerUserKeys`, masking, and response mapping. + - Exports `UserApiTokenSummary`, `GeneratedUserApiToken`, `getUserApiTokenSummaryFromBff`, `registerUserApiToken`, `maskSecret`. + +- `web/lib/cloudstack/api-tokens.test.ts` + - Tests `getUserKeys` mapping, `registerUserKeys` mapping, masking, safe POST body, and fallback. + +- `web/components/settings/api-token-actions.tsx` + - Optional client component for generating a new API key pair and rendering accessible success/error status. + +- `web/app/(app)/settings/api-tokens/page.tsx` + - Replaces placeholder empty state with token status and optional generation action. + +- `web/lib/cloudstack/security-settings.ts` + - Owns current-user security status derived from `listUsers` and mock fallback. + - May import mapping from `users.ts` after the profile slice lands. + +- `web/lib/cloudstack/security-settings.test.ts` + - Tests status mapping and fallback. + +- `web/components/settings/password-change-form.tsx` + - Optional client component if password change is implemented. + +- `web/app/(app)/settings/security/page.tsx` + - Replaces placeholder empty state with security status cards/rows. + +- `web/app/(app)/settings/page.tsx` + - Replaces placeholder empty state with a settings section index. + +- `web/tests/e2e/settings-pages.spec.ts` + - Updates browser coverage for active and still-placeholder settings pages. + +- `web/tests/e2e/README.md` + - Optional: document mocked settings BFF envelopes if tests add new patterns. + +- `web/messages/en.json` + - Shared message file. Each worker owns only its `Settings.pages.` subtree. + +- `web/lib/settings-messages.test.ts` + - Extend assertions for new settings keys. + +- `web/package.json` + - Optional: package metadata warning cleanup, only if verified safe. + +## Preflight + +### Task 0: Reopen Phase 5j Safely + +**Files:** +- Inspect only: `.worktrees/phase5j-*` +- Inspect only: `docs/superpowers/plans/2026-05-21-phase5j-settings-surfaces.md` + +- [ ] **Step 1: Confirm the main checkout is clean** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack +git status -sb +git log --oneline -5 +``` + +Expected: + +```text +## modernize-2026 + + +436457a385 Merge Phase 5i network page i18n +``` + +- [ ] **Step 2: Confirm the paused worktrees are clean** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack +git worktree list --porcelain +for wt in .worktrees/phase5j-profile-settings .worktrees/phase5j-api-token-settings .worktrees/phase5j-security-settings .worktrees/phase5j-settings-index .worktrees/phase5j-settings-e2e .worktrees/phase5j-package-metadata; do git -C "$wt" status -sb; done +``` + +Expected for each Phase 5j worktree: + +```text +## phase5j-... +``` + +The existing Phase 5j worktrees may report `436457a385` rather than the later doc-only plan commits; that is acceptable because those commits only add or clarify this plan document and workers should not edit the plan file. + +- [ ] **Step 3: If any Phase 5j worktree is missing, recreate it** + +Run only for missing worktrees: + +```bash +git worktree add -b phase5j-profile-settings .worktrees/phase5j-profile-settings modernize-2026 +git worktree add -b phase5j-api-token-settings .worktrees/phase5j-api-token-settings modernize-2026 +git worktree add -b phase5j-security-settings .worktrees/phase5j-security-settings modernize-2026 +git worktree add -b phase5j-settings-index .worktrees/phase5j-settings-index modernize-2026 +git worktree add -b phase5j-settings-e2e .worktrees/phase5j-settings-e2e modernize-2026 +git worktree add -b phase5j-package-metadata .worktrees/phase5j-package-metadata modernize-2026 +``` + +Expected: + +```text +Preparing worktree (new branch 'phase5j-...') +HEAD is now at 436457a385 Merge Phase 5i network page i18n +``` + +- [ ] **Step 4: Install web dependencies in worktrees as needed** + +Run inside each worktree before tests if `node_modules` is absent: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-profile-settings/web +npm ci +``` + +Expected: + +```text +added ... packages +``` + +Deprecation warnings are acceptable. Test failures are not. + +## Agent Dispatch Queue + +Start these in parallel when quota allows. If the current app still limits worker count, run five workers and keep one lane local. + +| Branch | Owner | Dependency | Merge order | +| --- | --- | --- | --- | +| `phase5j-profile-settings` | Profile helper and page | none | 1 | +| `phase5j-api-token-settings` | API token helper, page, action | none | 2 | +| `phase5j-security-settings` | Security helper and page | can later reuse `users.ts`, but should be self-contained first | 3 | +| `phase5j-settings-index` | Settings landing page | none | 4 | +| `phase5j-settings-e2e` | Browser coverage | easiest after page slices, but can draft before | 5 | +| `phase5j-package-metadata` | Optional module warning cleanup | none; merge last or discard | 6 | + +## Task 1: Profile Settings Surface + +**Files:** +- Create: `web/lib/cloudstack/users.ts` +- Create: `web/lib/cloudstack/users.test.ts` +- Modify: `web/app/(app)/settings/profile/page.tsx` +- Modify: `web/messages/en.json` under `Settings.pages.profile` +- Modify: `web/lib/settings-messages.test.ts` + +- [ ] **Step 1: Write the failing mapping test** + +Add `web/lib/cloudstack/users.test.ts`: + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; + +import { mockUser } from "../auth/mock.ts"; +import type { CurrentUser } from "../auth/types.ts"; +import { + getCurrentUserProfileFromBff, + mapCloudStackUserToProfile, + usersFromListUsersResponse, +} from "./users.ts"; + +const currentUser: CurrentUser = { + id: "user-uuid-1", + username: "alex", + email: "alex@cloudstack.local", + name: "Alex Kim", + role: "ROOT", + domain: "ROOT", + domainId: "domain-root", +}; + +test("mapCloudStackUserToProfile maps CloudStack user fields into profile rows", () => { + const profile = mapCloudStackUserToProfile({ + id: "user-uuid-1", + username: "alex", + firstname: "Alex", + lastname: "Kim", + email: "alex@example.test", + account: "admin", + accounttype: 1, + domain: "ROOT", + domainid: "domain-root", + timezone: "Australia/Perth", + usersource: "native", + state: "enabled", + apikeyaccess: true, + is2faenabled: true, + is2famandated: false, + }, currentUser); + + assert.deepEqual(profile, { + id: "user-uuid-1", + username: "alex", + displayName: "Alex Kim", + email: "alex@example.test", + account: "admin", + role: "ROOT", + domain: "ROOT", + domainId: "domain-root", + timezone: "Australia/Perth", + source: "native", + state: "enabled", + apiKeyAccess: "enabled", + twoFactorEnabled: true, + twoFactorMandated: false, + }); +}); + +test("usersFromListUsersResponse maps the listUsers envelope", () => { + const users = usersFromListUsersResponse({ + listusersresponse: { + count: 1, + user: [{ id: "user-uuid-1", username: "alex", firstname: "Alex", lastname: "Kim" }], + }, + }, currentUser); + + assert.equal(users.length, 1); + assert.equal(users[0]?.displayName, "Alex Kim"); +}); + +test("getCurrentUserProfileFromBff calls listUsers for the current user and forwards cookies", async () => { + const urls: URL[] = []; + const headers: HeadersInit[] = []; + const profile = await getCurrentUserProfileFromBff(currentUser, { + requestHeaders: new Headers({ host: "ui.example.test", cookie: "cloudstack.session=opaque" }), + fetchImpl: async (input, init) => { + urls.push(new URL(String(input))); + headers.push(init?.headers ?? {}); + return Response.json({ + listusersresponse: { + count: 1, + user: [{ id: "user-uuid-1", username: "alex", firstname: "Alex", lastname: "Kim" }], + }, + }); + }, + }); + + assert.equal(urls[0]?.pathname, "/api/cs/listUsers"); + assert.equal(urls[0]?.searchParams.get("id"), "user-uuid-1"); + assert.deepEqual(headers[0], { cookie: "cloudstack.session=opaque" }); + assert.equal(profile.displayName, "Alex Kim"); +}); + +test("getCurrentUserProfileFromBff falls back to Auth.js/mock identity when CloudStack is unavailable", async () => { + const previous = process.env.CS_URL; + delete process.env.CS_URL; + + try { + const profile = await getCurrentUserProfileFromBff(mockUser, { + fetchImpl: async () => { + throw new Error("should not fetch without CS_URL"); + }, + }); + + assert.equal(profile.displayName, mockUser.name); + assert.equal(profile.email, mockUser.email); + } finally { + process.env.CS_URL = previous; + } +}); +``` + +- [ ] **Step 2: Run the focused test and confirm it fails** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-profile-settings/web +node --test "lib/cloudstack/users.test.ts" +``` + +Expected: + +```text +Error [ERR_MODULE_NOT_FOUND]: Cannot find module .../lib/cloudstack/users.ts +``` + +- [ ] **Step 3: Implement `web/lib/cloudstack/users.ts`** + +Create: + +```ts +import { mockUser } from "../auth/mock.ts"; +import type { CurrentUser, Role } from "../auth/types.ts"; + +export type CloudStackUser = { + id?: string; + username?: string; + firstname?: string; + lastname?: string; + email?: string; + account?: string; + accounttype?: number | string; + roletype?: string; + rolename?: string; + domain?: string; + domainid?: string; + timezone?: string; + usersource?: string; + state?: string; + apikeyaccess?: boolean | string; + is2faenabled?: boolean | string; + is2famandated?: boolean | string; + isdefault?: boolean | string; +}; + +export type ListUsersResponse = { + listusersresponse?: { + count?: number | string; + user?: CloudStackUser[]; + }; +}; + +export type CloudStackUserProfile = { + id: string; + username: string; + displayName: string; + email: string; + account: string; + role: Role; + domain: string; + domainId: string; + timezone: string; + source: string; + state: string; + apiKeyAccess: "enabled" | "disabled" | "unknown"; + twoFactorEnabled: boolean; + twoFactorMandated: boolean; +}; + +type FetchOptions = { + fetchImpl?: typeof fetch; + requestHeaders?: Pick; +}; + +export async function getCurrentUserProfileFromBff( + currentUser: CurrentUser = mockUser, + { fetchImpl = fetch, requestHeaders }: FetchOptions = {}, +): Promise { + if (process.env.NEXT_PUBLIC_APP_ENV === "mock" || !process.env.CS_URL) { + return profileFromCurrentUser(currentUser); + } + + try { + const response = await fetchImpl(buildListUsersUrl(currentUser.id, requestHeaders), { + method: "GET", + cache: "no-store", + headers: buildForwardedHeaders(requestHeaders), + }); + + if (!response.ok) { + return profileFromCurrentUser(currentUser); + } + + const payload = (await response.json()) as ListUsersResponse; + const profile = usersFromListUsersResponse(payload, currentUser)[0]; + return profile ?? profileFromCurrentUser(currentUser); + } catch { + return profileFromCurrentUser(currentUser); + } +} + +export function usersFromListUsersResponse( + response: ListUsersResponse, + currentUser: CurrentUser = mockUser, +): CloudStackUserProfile[] { + return (response.listusersresponse?.user ?? []).map((user) => mapCloudStackUserToProfile(user, currentUser)); +} + +export function mapCloudStackUserToProfile( + user: CloudStackUser, + currentUser: CurrentUser = mockUser, +): CloudStackUserProfile { + return { + id: user.id ?? currentUser.id, + username: user.username ?? currentUser.username, + displayName: formatDisplayName(user, currentUser), + email: user.email ?? currentUser.email, + account: user.account ?? currentUser.username, + role: mapRole(user, currentUser.role), + domain: user.domain ?? currentUser.domain, + domainId: user.domainid ?? currentUser.domainId, + timezone: user.timezone ?? "Browser default", + source: user.usersource ?? "unknown", + state: user.state ?? "unknown", + apiKeyAccess: mapApiKeyAccess(user.apikeyaccess), + twoFactorEnabled: readBoolean(user.is2faenabled), + twoFactorMandated: readBoolean(user.is2famandated), + }; +} + +function profileFromCurrentUser(user: CurrentUser): CloudStackUserProfile { + return { + id: user.id, + username: user.username, + displayName: user.name, + email: user.email, + account: user.username, + role: user.role, + domain: user.domain, + domainId: user.domainId, + timezone: "Browser default", + source: "session", + state: "active", + apiKeyAccess: "unknown", + twoFactorEnabled: false, + twoFactorMandated: false, + }; +} + +function formatDisplayName(user: CloudStackUser, currentUser: CurrentUser): string { + const name = [user.firstname, user.lastname].filter(Boolean).join(" ").trim(); + return name || currentUser.name || user.username || "Unknown user"; +} + +function mapRole(user: CloudStackUser, fallback: Role): Role { + const text = `${user.roletype ?? ""} ${user.rolename ?? ""} ${user.accounttype ?? ""}`.toLowerCase(); + if (text.includes("root") || text.includes("admin") || text.includes("1")) { + return "ROOT"; + } + if (text.includes("resource")) { + return "ADMIN"; + } + if (text.includes("domain") || text.includes("2")) { + return "DOMAIN_ADMIN"; + } + return fallback; +} + +function mapApiKeyAccess(value: CloudStackUser["apikeyaccess"]): CloudStackUserProfile["apiKeyAccess"] { + if (value === true || String(value).toLowerCase() === "enabled" || String(value).toLowerCase() === "true") { + return "enabled"; + } + if (value === false || String(value).toLowerCase() === "disabled" || String(value).toLowerCase() === "false") { + return "disabled"; + } + return "unknown"; +} + +function readBoolean(value: boolean | string | undefined): boolean { + return value === true || String(value).toLowerCase() === "true"; +} + +export function buildListUsersUrl(userId: string, requestHeaders?: Pick): string { + const params = new URLSearchParams({ id: userId, showicon: "true" }); + return `${getRequestOrigin(requestHeaders)}/api/cs/listUsers?${params.toString()}`; +} + +function getRequestOrigin(requestHeaders?: Pick): string { + if (process.env.NEXTAUTH_URL) { + return process.env.NEXTAUTH_URL.replace(/\/$/, ""); + } + + const host = requestHeaders?.get("x-forwarded-host") ?? requestHeaders?.get("host") ?? "localhost:3000"; + const protocol = requestHeaders?.get("x-forwarded-proto") ?? (host.startsWith("localhost") ? "http" : "https"); + return `${protocol}://${host}`; +} + +function buildForwardedHeaders(requestHeaders?: Pick): HeadersInit | undefined { + const cookie = requestHeaders?.get("cookie"); + return cookie ? { cookie } : undefined; +} +``` + +- [ ] **Step 4: Replace the profile page placeholder** + +Modify `web/app/(app)/settings/profile/page.tsx`: + +```tsx +import type { Metadata } from "next"; +import { headers } from "next/headers"; +import { getTranslations } from "next-intl/server"; + +import { PageHeader } from "@/components/page-header"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { getCurrentUser } from "@/lib/auth/server"; +import { getCurrentUserProfileFromBff } from "@/lib/cloudstack/users"; + +export async function generateMetadata(): Promise { + const t = await getTranslations("Settings.pages.profile"); + return { title: t("metadataTitle") }; +} + +export default async function Page() { + const t = await getTranslations("Settings.pages.profile"); + const currentUser = await getCurrentUser(); + const profile = await getCurrentUserProfileFromBff(currentUser, { requestHeaders: headers() }); + + const rows = [ + [t("fields.username"), profile.username], + [t("fields.email"), profile.email], + [t("fields.account"), profile.account], + [t("fields.domain"), profile.domain], + [t("fields.timezone"), profile.timezone], + [t("fields.source"), profile.source], + ] as const; + + return ( + <> + +
+ + + {profile.displayName} + + +
+ {rows.map(([label, value]) => ( +
+
{label}
+
{value}
+
+ ))} +
+
+
+ + + {t("summary.title")} + + + + + + + + {profile.state} + + + +
+ + ); +} + +function SummaryRow({ label, value }: { label: string; value: string }) { + return ( +
+ {label} + {value} +
+ ); +} +``` + +If `CardTitle` is not exported by `web/components/ui/card.tsx`, use the existing local card markup pattern from other pages instead of adding a new primitive. + +- [ ] **Step 5: Add profile messages** + +Modify only `Settings.pages.profile` in `web/messages/en.json`: + +```json +"profile": { + "metadataTitle": "Profile settings", + "title": "Profile", + "description": "Current CloudStack identity, scope, and console preferences.", + "fields": { + "username": "Username", + "email": "Email", + "account": "Account", + "domain": "Domain", + "timezone": "Timezone", + "source": "Source" + }, + "summary": { + "title": "Account summary", + "role": "Role", + "state": "State", + "apiKeyAccess": "API key access", + "twoFactor": "Two-factor authentication" + }, + "states": { + "enabled": "Enabled", + "disabled": "Disabled" + }, + "apiKeyAccess": { + "enabled": "Enabled", + "disabled": "Disabled", + "unknown": "Not reported" + } +} +``` + +- [ ] **Step 6: Extend settings message test** + +Update `web/lib/settings-messages.test.ts` to assert the new profile keys: + +```ts +const SETTINGS_MESSAGE_KEYS = [ + "pages.profile.fields.username", + "pages.profile.fields.email", + "pages.profile.summary.title", + "pages.profile.apiKeyAccess.enabled", +] as const; + +test("settings profile resolves operational message keys", () => { + for (const key of SETTINGS_MESSAGE_KEYS) { + assert.equal(typeof readSettingsMessage(key), "string", key); + } +}); +``` + +- [ ] **Step 7: Verify and commit** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-profile-settings/web +npm run test:unit +npm run typecheck +npm run lint +cd .. +git diff --check +git status -sb +git add web/lib/cloudstack/users.ts web/lib/cloudstack/users.test.ts 'web/app/(app)/settings/profile/page.tsx' web/messages/en.json web/lib/settings-messages.test.ts +git commit -m "Add CloudStack-backed profile settings" +``` + +Expected: + +```text +# tests pass +[phase5j-profile-settings ...] Add CloudStack-backed profile settings +``` + +## Task 2: API Token Settings Surface + +**Files:** +- Create: `web/lib/cloudstack/api-tokens.ts` +- Create: `web/lib/cloudstack/api-tokens.test.ts` +- Create: `web/components/settings/api-token-actions.tsx` +- Modify: `web/app/(app)/settings/api-tokens/page.tsx` +- Modify: `web/messages/en.json` under `Settings.pages.apiTokens` +- Modify: `web/lib/settings-messages.test.ts` + +- [ ] **Step 1: Write API token tests** + +Create `web/lib/cloudstack/api-tokens.test.ts`: + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; + +import { + getUserApiTokenSummaryFromBff, + mapGetUserKeysResponse, + mapRegisterUserKeysResponse, + maskSecret, + registerUserApiToken, +} from "./api-tokens.ts"; + +test("maskSecret preserves short inspection prefix and suffix", () => { + assert.equal(maskSecret("abcdefghijklmnop"), "abcd...mnop"); + assert.equal(maskSecret("abc"), "••••"); + assert.equal(maskSecret(null), "Not generated"); +}); + +test("mapGetUserKeysResponse maps API key access and masks sensitive values", () => { + const summary = mapGetUserKeysResponse({ + getuserkeysresponse: { + userkeys: { + apikeyaccess: true, + apikey: "api-key-123456", + secretkey: "secret-key-abcdef", + }, + }, + }); + + assert.deepEqual(summary, { + access: "enabled", + apiKeyMasked: "api-...3456", + secretKeyMasked: "secr...cdef", + hasApiKey: true, + hasSecretKey: true, + }); +}); + +test("mapRegisterUserKeysResponse maps generated key pair without dropping the one-time secret", () => { + const generated = mapRegisterUserKeysResponse({ + registeruserkeysresponse: { + userkeys: { + id: "keypair-1", + name: "automation", + apikey: "generated-api", + secretkey: "generated-secret", + }, + }, + }); + + assert.equal(generated.id, "keypair-1"); + assert.equal(generated.apiKey, "generated-api"); + assert.equal(generated.secretKey, "generated-secret"); +}); + +test("getUserApiTokenSummaryFromBff calls getUserKeys for the current user", async () => { + const urls: URL[] = []; + const summary = await getUserApiTokenSummaryFromBff("user-1", { + requestHeaders: new Headers({ host: "ui.example.test", cookie: "cloudstack.session=opaque" }), + fetchImpl: async (input) => { + urls.push(new URL(String(input))); + return Response.json({ getuserkeysresponse: { userkeys: { apikeyaccess: false } } }); + }, + }); + + assert.equal(urls[0]?.pathname, "/api/cs/getUserKeys"); + assert.equal(urls[0]?.searchParams.get("id"), "user-1"); + assert.equal(summary.access, "disabled"); +}); + +test("registerUserApiToken posts safe JSON params to registerUserKeys", async () => { + const calls: Array<{ url: URL; body: unknown }> = []; + await registerUserApiToken({ + userId: "user-1", + name: "automation", + description: "Created from settings", + fetchImpl: async (input, init) => { + calls.push({ url: new URL(String(input), "http://ui.example.test"), body: JSON.parse(String(init?.body)) }); + return Response.json({ + registeruserkeysresponse: { + userkeys: { id: "keypair-1", apikey: "generated-api", secretkey: "generated-secret" }, + }, + }); + }, + }); + + assert.equal(calls[0]?.url.pathname, "/api/cs/registerUserKeys"); + assert.deepEqual(calls[0]?.body, { + id: "user-1", + name: "automation", + description: "Created from settings", + }); +}); +``` + +- [ ] **Step 2: Run the focused test and confirm it fails** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-api-token-settings/web +node --test "lib/cloudstack/api-tokens.test.ts" +``` + +Expected: + +```text +Error [ERR_MODULE_NOT_FOUND]: Cannot find module .../lib/cloudstack/api-tokens.ts +``` + +- [ ] **Step 3: Implement `api-tokens.ts`** + +Create a helper with these exported contracts: + +```ts +export type UserApiTokenSummary = { + access: "enabled" | "disabled" | "unknown"; + apiKeyMasked: string; + secretKeyMasked: string; + hasApiKey: boolean; + hasSecretKey: boolean; +}; + +export type GeneratedUserApiToken = { + id: string | null; + name: string; + apiKey: string; + secretKey: string; +}; + +export function maskSecret(value: string | null | undefined): string { + if (!value || value.length < 8) { + return value ? "••••" : "Not generated"; + } + return `${value.slice(0, 4)}...${value.slice(-4)}`; +} +``` + +Implementation details: + +- `getUserApiTokenSummaryFromBff(userId, options)`: + - Return mock summary if `NEXT_PUBLIC_APP_ENV === "mock"` or `!process.env.CS_URL`. + - Fetch `GET /api/cs/getUserKeys?id=`. + - Forward only `cookie`. + - Return disabled/unknown summary on non-OK, malformed envelope, or thrown fetch. + +- `registerUserApiToken(input)`: + - POST `/api/cs/registerUserKeys`. + - Body: `{ id, name, description }`; omit blank description. + - Throw CloudStack error text if `errorresponse.errortext` exists. + - Throw if generated envelope lacks `apikey` or `secretkey`. + +- Accept both response shapes: + - `getuserkeysresponse.userkeys` + - `registeruserkeysresponse.userkeys` + - direct `registeruserkeysresponse.apikey`/`secretkey` if CloudStack returns a flatter response. + +- [ ] **Step 4: Add client action component** + +Create `web/components/settings/api-token-actions.tsx`: + +```tsx +"use client"; + +import { useState, useTransition } from "react"; + +import { Button } from "@/components/ui/button"; +import { registerUserApiToken, type GeneratedUserApiToken } from "@/lib/cloudstack/api-tokens"; + +type ApiTokenActionsProps = { + userId: string; + generateLabel: string; + successLabel: string; + errorLabel: string; +}; + +export function ApiTokenActions({ userId, generateLabel, successLabel, errorLabel }: ApiTokenActionsProps) { + const [isPending, startTransition] = useTransition(); + const [generated, setGenerated] = useState(null); + const [error, setError] = useState(null); + + return ( +
+ + {generated ? ( +
+

{successLabel}

+

API: {generated.apiKey}

+

Secret: {generated.secretKey}

+
+ ) : null} + {error ?

{error}

: null} +
+ ); +} +``` + +If showing the full generated secret is considered too sensitive during review, change the component to show `maskSecret(generated.secretKey)` and add a one-time copy button only in a later dedicated slice. + +- [ ] **Step 5: Replace API token page placeholder** + +Modify `web/app/(app)/settings/api-tokens/page.tsx`: + +```tsx +import type { Metadata } from "next"; +import { headers } from "next/headers"; +import { getTranslations } from "next-intl/server"; + +import { PageHeader } from "@/components/page-header"; +import { ApiTokenActions } from "@/components/settings/api-token-actions"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { getCurrentUser } from "@/lib/auth/server"; +import { getUserApiTokenSummaryFromBff } from "@/lib/cloudstack/api-tokens"; + +export async function generateMetadata(): Promise { + const t = await getTranslations("Settings.pages.apiTokens"); + return { title: t("metadataTitle") }; +} + +export default async function Page() { + const t = await getTranslations("Settings.pages.apiTokens"); + const user = await getCurrentUser(); + const summary = await getUserApiTokenSummaryFromBff(user.id, { requestHeaders: headers() }); + + return ( + <> + +
+ + + {t("current.title")} + + + + + + + {t(`access.${summary.access}`)} + + + + + + {t("generate.title")} + + +

{t("generate.description")}

+ +
+
+
+ + ); +} + +function TokenRow({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) { + return ( +
+ {label} + {value} +
+ ); +} +``` + +- [ ] **Step 6: Add API token messages and test keys** + +Add to `Settings.pages.apiTokens`: + +```json +"current": { + "title": "Current key status", + "access": "API key access", + "apiKey": "API key", + "secretKey": "Secret key" +}, +"access": { + "enabled": "Enabled", + "disabled": "Disabled", + "unknown": "Not reported" +}, +"generate": { + "title": "Generate key pair", + "description": "Generate a new CloudStack API key pair for automation. The secret is returned once by CloudStack.", + "action": "Generate key pair", + "success": "Generated key pair", + "error": "Unable to generate API key pair" +} +``` + +Extend `web/lib/settings-messages.test.ts` with: + +```ts +const API_TOKEN_MESSAGE_KEYS = [ + "pages.apiTokens.current.title", + "pages.apiTokens.current.apiKey", + "pages.apiTokens.generate.action", + "pages.apiTokens.access.enabled", +] as const; +``` + +- [ ] **Step 7: Verify and commit** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-api-token-settings/web +npm run test:unit +npm run typecheck +npm run lint +cd .. +git diff --check +git add web/lib/cloudstack/api-tokens.ts web/lib/cloudstack/api-tokens.test.ts web/components/settings/api-token-actions.tsx 'web/app/(app)/settings/api-tokens/page.tsx' web/messages/en.json web/lib/settings-messages.test.ts +git commit -m "Add CloudStack API token settings" +``` + +## Task 3: Security Settings Surface + +**Files:** +- Create: `web/lib/cloudstack/security-settings.ts` +- Create: `web/lib/cloudstack/security-settings.test.ts` +- Optional create: `web/components/settings/password-change-form.tsx` +- Modify: `web/app/(app)/settings/security/page.tsx` +- Modify: `web/messages/en.json` under `Settings.pages.security` +- Modify: `web/lib/settings-messages.test.ts` + +- [ ] **Step 1: Write security mapping tests** + +Create `web/lib/cloudstack/security-settings.test.ts`: + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; + +import { mockUser } from "../auth/mock.ts"; +import { + getCurrentUserSecuritySettingsFromBff, + mapCloudStackUserToSecuritySettings, +} from "./security-settings.ts"; + +test("mapCloudStackUserToSecuritySettings maps current user security flags", () => { + const settings = mapCloudStackUserToSecuritySettings({ + id: "user-1", + usersource: "native", + state: "enabled", + apikeyaccess: "Enabled", + is2faenabled: true, + is2famandated: false, + passwordchangerequired: true, + }); + + assert.deepEqual(settings, { + source: "native", + state: "enabled", + apiKeyAccess: "enabled", + twoFactorEnabled: true, + twoFactorMandated: false, + passwordChangeRequired: true, + }); +}); + +test("getCurrentUserSecuritySettingsFromBff falls back when CS_URL is absent", async () => { + const previous = process.env.CS_URL; + delete process.env.CS_URL; + + try { + const settings = await getCurrentUserSecuritySettingsFromBff(mockUser, { + fetchImpl: async () => { + throw new Error("should not fetch without CS_URL"); + }, + }); + assert.equal(settings.source, "session"); + assert.equal(settings.apiKeyAccess, "unknown"); + } finally { + process.env.CS_URL = previous; + } +}); +``` + +- [ ] **Step 2: Implement `security-settings.ts`** + +Create: + +```ts +import type { CurrentUser } from "../auth/types.ts"; +import { type CloudStackUser, type ListUsersResponse, buildListUsersUrl } from "./users.ts"; + +export type CurrentUserSecuritySettings = { + source: string; + state: string; + apiKeyAccess: "enabled" | "disabled" | "unknown"; + twoFactorEnabled: boolean; + twoFactorMandated: boolean; + passwordChangeRequired: boolean; +}; + +type FetchOptions = { + fetchImpl?: typeof fetch; + requestHeaders?: Pick; +}; + +export async function getCurrentUserSecuritySettingsFromBff( + user: CurrentUser, + { fetchImpl = fetch, requestHeaders }: FetchOptions = {}, +): Promise { + if (process.env.NEXT_PUBLIC_APP_ENV === "mock" || !process.env.CS_URL) { + return fallbackSecuritySettings(); + } + + try { + const response = await fetchImpl(buildListUsersUrl(user.id, requestHeaders), { + method: "GET", + cache: "no-store", + headers: buildForwardedHeaders(requestHeaders), + }); + if (!response.ok) { + return fallbackSecuritySettings(); + } + const payload = (await response.json()) as ListUsersResponse; + const cloudStackUser = payload.listusersresponse?.user?.[0]; + return cloudStackUser ? mapCloudStackUserToSecuritySettings(cloudStackUser) : fallbackSecuritySettings(); + } catch { + return fallbackSecuritySettings(); + } +} + +export function mapCloudStackUserToSecuritySettings(user: CloudStackUser): CurrentUserSecuritySettings { + return { + source: user.usersource ?? "unknown", + state: user.state ?? "unknown", + apiKeyAccess: mapAccess(user.apikeyaccess), + twoFactorEnabled: readBoolean(user.is2faenabled), + twoFactorMandated: readBoolean(user.is2famandated), + passwordChangeRequired: readBoolean((user as CloudStackUser & { passwordchangerequired?: boolean | string }).passwordchangerequired), + }; +} + +function fallbackSecuritySettings(): CurrentUserSecuritySettings { + return { + source: "session", + state: "active", + apiKeyAccess: "unknown", + twoFactorEnabled: false, + twoFactorMandated: false, + passwordChangeRequired: false, + }; +} + +function mapAccess(value: CloudStackUser["apikeyaccess"]): CurrentUserSecuritySettings["apiKeyAccess"] { + if (value === true || String(value).toLowerCase() === "enabled" || String(value).toLowerCase() === "true") return "enabled"; + if (value === false || String(value).toLowerCase() === "disabled" || String(value).toLowerCase() === "false") return "disabled"; + return "unknown"; +} + +function readBoolean(value: boolean | string | undefined): boolean { + return value === true || String(value).toLowerCase() === "true"; +} + +function buildForwardedHeaders(requestHeaders?: Pick): HeadersInit | undefined { + const cookie = requestHeaders?.get("cookie"); + return cookie ? { cookie } : undefined; +} +``` + +If this branch runs before Task 1 lands, copy the minimal `CloudStackUser`, `ListUsersResponse`, and `buildListUsersUrl` definitions locally. During coordinator merge, replace the duplicate with imports from `users.ts`. + +- [ ] **Step 3: Replace the security page placeholder** + +Modify `web/app/(app)/settings/security/page.tsx`: + +```tsx +import type { Metadata } from "next"; +import { headers } from "next/headers"; +import { getTranslations } from "next-intl/server"; + +import { PageHeader } from "@/components/page-header"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { getCurrentUser } from "@/lib/auth/server"; +import { getCurrentUserSecuritySettingsFromBff } from "@/lib/cloudstack/security-settings"; + +export async function generateMetadata(): Promise { + const t = await getTranslations("Settings.pages.security"); + return { title: t("metadataTitle") }; +} + +export default async function Page() { + const t = await getTranslations("Settings.pages.security"); + const user = await getCurrentUser(); + const settings = await getCurrentUserSecuritySettingsFromBff(user, { requestHeaders: headers() }); + + const rows = [ + [t("fields.source"), settings.source], + [t("fields.state"), settings.state], + [t("fields.apiKeyAccess"), t(`access.${settings.apiKeyAccess}`)], + [t("fields.twoFactorEnabled"), settings.twoFactorEnabled ? t("states.enabled") : t("states.disabled")], + [t("fields.twoFactorMandated"), settings.twoFactorMandated ? t("states.enabled") : t("states.disabled")], + [t("fields.passwordChangeRequired"), settings.passwordChangeRequired ? t("states.required") : t("states.notRequired")], + ] as const; + + return ( + <> + + + + {t("summary.title")} + + +
+ {rows.map(([label, value]) => ( +
+
{label}
+
{value}
+
+ ))} +
+
+ {t("badges.twoFactor")} + {t("badges.apiKeyAccess")} +
+
+
+ + ); +} +``` + +- [ ] **Step 4: Add security messages and tests** + +Add to `Settings.pages.security`: + +```json +"summary": { + "title": "Security status" +}, +"fields": { + "source": "Authentication source", + "state": "User state", + "apiKeyAccess": "API key access", + "twoFactorEnabled": "Two-factor enabled", + "twoFactorMandated": "Two-factor mandated", + "passwordChangeRequired": "Password change" +}, +"states": { + "enabled": "Enabled", + "disabled": "Disabled", + "required": "Required", + "notRequired": "Not required" +}, +"access": { + "enabled": "Enabled", + "disabled": "Disabled", + "unknown": "Not reported" +}, +"badges": { + "twoFactor": "Two-factor", + "apiKeyAccess": "API keys" +} +``` + +Extend `web/lib/settings-messages.test.ts` with security keys. + +- [ ] **Step 5: Verify and commit** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-security-settings/web +npm run test:unit +npm run typecheck +npm run lint +cd .. +git diff --check +git add web/lib/cloudstack/security-settings.ts web/lib/cloudstack/security-settings.test.ts 'web/app/(app)/settings/security/page.tsx' web/messages/en.json web/lib/settings-messages.test.ts +git commit -m "Add CloudStack security settings" +``` + +## Task 4: Settings Landing Page + +**Files:** +- Modify: `web/app/(app)/settings/page.tsx` +- Modify: `web/messages/en.json` under `Settings.pages.index` +- Modify: `web/lib/settings-messages.test.ts` + +- [ ] **Step 1: Replace the settings index placeholder** + +Modify `web/app/(app)/settings/page.tsx`: + +```tsx +import type { Metadata } from "next"; +import Link from "next/link"; +import { getTranslations } from "next-intl/server"; + +import { PageHeader } from "@/components/page-header"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +const SECTIONS = [ + { key: "profile", href: "/settings/profile", state: "available" }, + { key: "security", href: "/settings/security", state: "available" }, + { key: "apiTokens", href: "/settings/api-tokens", state: "available" }, + { key: "integrations", href: "/settings/integrations", state: "notConfigured" }, + { key: "billing", href: "/settings/billing", state: "notConfigured" }, + { key: "notifications", href: "/settings/notifications", state: "notConfigured" }, + { key: "advanced", href: "/settings/advanced", state: "notConfigured" }, +] as const; + +export async function generateMetadata(): Promise { + const t = await getTranslations("Settings.pages.index"); + return { title: t("metadataTitle") }; +} + +export default async function Page() { + const t = await getTranslations("Settings.pages.index"); + + return ( + <> + +
+ {SECTIONS.map((section) => ( + + + +
+ {t(`sections.${section.key}.title`)} + + {t(`states.${section.state}`)} + +
+
+ +

{t(`sections.${section.key}.description`)}

+
+
+ + ))} +
+ + ); +} +``` + +- [ ] **Step 2: Add index messages** + +Replace `Settings.pages.index.emptyState` with: + +```json +"states": { + "available": "Available", + "notConfigured": "Not configured" +}, +"sections": { + "profile": { + "title": "Profile", + "description": "Current CloudStack user, account, domain, and console preferences." + }, + "security": { + "title": "Security", + "description": "Authentication source, two-factor status, API key access, and session posture." + }, + "apiTokens": { + "title": "API tokens", + "description": "CloudStack API key visibility and scoped key-pair generation." + }, + "integrations": { + "title": "Integrations", + "description": "Identity, monitoring, and automation integrations for this scope." + }, + "billing": { + "title": "Billing", + "description": "Invoices, usage exports, and payment configuration." + }, + "notifications": { + "title": "Notifications", + "description": "Email, event, and operational alert preferences." + }, + "advanced": { + "title": "Advanced", + "description": "Low-level console and experimental operator controls." + } +} +``` + +- [ ] **Step 3: Verify and commit** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-settings-index/web +npm run test:unit +npm run typecheck +npm run lint +cd .. +git diff --check +git add 'web/app/(app)/settings/page.tsx' web/messages/en.json web/lib/settings-messages.test.ts +git commit -m "Add settings landing surface" +``` + +## Task 5: Settings Browser Coverage + +**Files:** +- Modify: `web/tests/e2e/settings-pages.spec.ts` +- Optional modify: `web/tests/e2e/README.md` + +- [ ] **Step 1: Update e2e test framing** + +Replace the current `test.describe("settings placeholder pages", ...)` with: + +```ts +import { expect, test } from "./fixtures/cloudstack-bff"; + +test.describe("settings surfaces", () => { + test("settings index links to active and not-configured sections", async ({ page }) => { + await page.goto("/settings"); + + await expect(page.getByRole("heading", { name: "Settings" })).toBeVisible(); + await expect(page.getByRole("link", { name: /Profile/ })).toBeVisible(); + await expect(page.getByRole("link", { name: /Security/ })).toBeVisible(); + await expect(page.getByRole("link", { name: /API tokens/ })).toBeVisible(); + await expect(page.getByText("Available")).toHaveCount(3); + }); + + test("profile page renders current user identity from listUsers", async ({ page, mockCloudStackBff }) => { + mockCloudStackBff.use("listUsers", { + listusersresponse: { + count: 1, + user: [{ + id: "mock-uuid-alex", + username: "alex", + firstname: "Alex", + lastname: "Kim", + email: "alex@example.test", + account: "admin", + domain: "ROOT", + timezone: "Australia/Perth", + usersource: "native", + state: "enabled", + apikeyaccess: true, + is2faenabled: true, + }], + }, + }); + + await page.goto("/settings/profile"); + + await expect(page.getByRole("heading", { name: "Profile" })).toBeVisible(); + await expect(page.getByText("Alex Kim")).toBeVisible(); + await expect(page.getByText("alex@example.test")).toBeVisible(); + expect(mockCloudStackBff.calls("listUsers").at(-1)?.params.get("id")).toBe("mock-uuid-alex"); + }); + + test("security page renders current user security flags from listUsers", async ({ page, mockCloudStackBff }) => { + mockCloudStackBff.use("listUsers", { + listusersresponse: { + count: 1, + user: [{ + id: "mock-uuid-alex", + username: "alex", + usersource: "native", + state: "enabled", + apikeyaccess: true, + is2faenabled: true, + is2famandated: false, + }], + }, + }); + + await page.goto("/settings/security"); + + await expect(page.getByRole("heading", { name: "Security" })).toBeVisible(); + await expect(page.getByText("Authentication source")).toBeVisible(); + await expect(page.getByText("native")).toBeVisible(); + }); + + test("API token page renders getUserKeys status and can request key generation", async ({ page, mockCloudStackBff }) => { + mockCloudStackBff.use("getUserKeys", { + getuserkeysresponse: { + userkeys: { + apikeyaccess: true, + apikey: "api-key-12345678", + secretkey: "secret-key-abcdefgh", + }, + }, + }); + mockCloudStackBff.use("registerUserKeys", { + registeruserkeysresponse: { + userkeys: { + id: "keypair-generated", + apikey: "generated-api-key", + secretkey: "generated-secret-key", + }, + }, + }); + + await page.goto("/settings/api-tokens"); + + await expect(page.getByRole("heading", { name: "API tokens" })).toBeVisible(); + await expect(page.getByText("api-...5678")).toBeVisible(); + await page.getByRole("button", { name: "Generate key pair" }).click(); + await expect(page.getByRole("status")).toContainText("Generated key pair"); + expect(mockCloudStackBff.calls("registerUserKeys").at(-1)?.json).toMatchObject({ + id: "mock-uuid-alex", + }); + }); +}); + +const placeholderPages = [ + ["/settings/integrations", "Integrations", "No integrations configured"], + ["/settings/billing", "Billing", "No billing settings available"], + ["/settings/notifications", "Notifications", "No notification preferences configured"], + ["/settings/advanced", "Advanced", "No advanced controls available"], +] as const; + +test.describe("settings not-configured pages", () => { + for (const [path, heading, emptyTitle] of placeholderPages) { + test(`${path} keeps stable not-configured state`, async ({ page }) => { + await page.goto(path); + await expect(page.getByRole("heading", { name: heading })).toBeVisible(); + await expect(page.getByText(emptyTitle)).toBeVisible(); + }); + } +}); +``` + +If the e2e branch runs before page branches merge, keep assertions compatible by allowing either the new operational text or the old empty-state text, then tighten assertions after coordinator merge. + +- [ ] **Step 2: Verify and commit** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-settings-e2e/web +PLAYWRIGHT_PORT=3156 npm run test:e2e -- tests/e2e/settings-pages.spec.ts +npm run typecheck +cd .. +git diff --check +git add web/tests/e2e/settings-pages.spec.ts web/tests/e2e/README.md +git commit -m "Add settings surface browser coverage" +``` + +If Playwright fails with `EPERM` binding `127.0.0.1`, rerun the same command with sandbox escalation. + +## Task 6: Optional Package Metadata Warning Cleanup + +**Files:** +- Modify only if safe: `web/package.json` + +- [ ] **Step 1: Reproduce the warning** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-package-metadata/web +npm run test:unit +``` + +Expected current warning: + +```text +MODULE_TYPELESS_PACKAGE_JSON +``` + +- [ ] **Step 2: Add ESM package metadata** + +Modify `web/package.json`: + +```json +{ + "name": "cloudstack-web", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Modern role-based Apache CloudStack UI (Phase 5 rebuild)" +} +``` + +Keep all existing scripts and dependencies unchanged. + +- [ ] **Step 3: Run full web verification in the branch** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-package-metadata/web +npm run test:unit +npm run typecheck +npm run lint +npm run build +PLAYWRIGHT_PORT=3157 npm run test:e2e -- tests/e2e/app-shell.spec.ts +``` + +Expected: + +```text +# unit tests pass without MODULE_TYPELESS_PACKAGE_JSON warning +# typecheck/lint/build pass +# focused e2e passes +``` + +- [ ] **Step 4: Abort if config churn starts** + +If adding `"type": "module"` requires changing Next config, Playwright config, TS config, imports, or more than `web/package.json`, revert this branch: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-package-metadata +git diff +git restore web/package.json +git status -sb +``` + +Expected: + +```text +## phase5j-package-metadata +``` + +- [ ] **Step 5: Commit only if verification is green** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/.worktrees/phase5j-package-metadata +git diff --check +git add web/package.json +git commit -m "Mark web package as ESM" +``` + +If no commit is made, report `no commit; cleanup unsafe or unnecessary`. + +## Task 7: Coordinator Merge And Verification + +**Files:** +- Modify during merge: `web/messages/en.json` +- Modify during merge: `web/lib/settings-messages.test.ts` +- Modify after green batch: `/Users/damian/Claude/HANDOVER.md` + +- [ ] **Step 1: Merge profile first** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack +git checkout modernize-2026 +git merge --no-ff phase5j-profile-settings -m "Merge Phase 5j profile settings" +``` + +Expected: + +```text +Merge made by the 'ort' strategy. +``` + +- [ ] **Step 2: Merge API token slice** + +Run: + +```bash +git merge --no-ff phase5j-api-token-settings -m "Merge Phase 5j API token settings" +``` + +If `web/messages/en.json` conflicts, resolve by preserving both `Settings.pages.profile` and `Settings.pages.apiTokens`. + +- [ ] **Step 3: Merge security slice** + +Run: + +```bash +git merge --no-ff phase5j-security-settings -m "Merge Phase 5j security settings" +``` + +If `security-settings.ts` duplicated types from `users.ts`, refactor to import from `./users.ts` before committing the merge resolution. + +- [ ] **Step 4: Merge settings index** + +Run: + +```bash +git merge --no-ff phase5j-settings-index -m "Merge Phase 5j settings index" +``` + +Preserve all `Settings.pages.index`, `profile`, `security`, and `apiTokens` keys. + +- [ ] **Step 5: Merge settings e2e** + +Run: + +```bash +git merge --no-ff phase5j-settings-e2e -m "Merge Phase 5j settings browser coverage" +``` + +Tighten e2e assertions if the branch used compatibility assertions before the page slices landed. + +- [ ] **Step 6: Merge or discard package metadata** + +If the package metadata branch has a green commit: + +```bash +git merge --no-ff phase5j-package-metadata -m "Merge Phase 5j package metadata cleanup" +``` + +If it has no commit: + +```bash +git branch -D phase5j-package-metadata +``` + +Only delete the branch if its worktree has already been removed or if Git allows it. + +- [ ] **Step 7: Run full web verification** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack/web +npm run test:unit +npm run typecheck +npm run lint +npm run build +PLAYWRIGHT_PORT=3158 npm run test:e2e +``` + +Expected: + +```text +# all unit tests pass +# typecheck passes +# lint passes +# Next build passes +# Playwright Chromium tests pass +``` + +- [ ] **Step 8: Run root verification** + +Run: + +```bash +cd /Users/damian/Claude/cloudstack +git diff --check +git status -sb +git log --oneline -12 +``` + +Expected: + +```text +## modernize-2026 +``` + +- [ ] **Step 9: Push** + +Run: + +```bash +git push origin modernize-2026 +``` + +Expected: + +```text +modernize-2026 -> modernize-2026 +``` + +- [ ] **Step 10: Update handover** + +Add a new top section to `/Users/damian/Claude/HANDOVER.md`: + +```markdown +=== Latest Codex handback (Phase 5j settings surfaces landed) === + +HEAD at handback checkpoint: (`origin/modernize-2026` pushed after verification) + +What landed: +- CloudStack-backed Profile settings page via `listUsers`. +- CloudStack-backed Security settings status via `listUsers`. +- CloudStack API-token status and key generation via `getUserKeys`/`registerUserKeys`. +- Practical Settings landing page. +- Settings browser coverage for active and not-configured settings routes. +- Optional package metadata cleanup: . + +Verification: +- `cd web && npm run test:unit` +- `cd web && npm run typecheck` +- `cd web && npm run lint` +- `cd web && npm run build` +- `cd web && PLAYWRIGHT_PORT=3158 npm run test:e2e` +- `git diff --check` + +Next useful work: +1. Implement integrations, notifications, billing settings only once real CloudStack endpoints are selected. +2. Continue converting mock fallback surfaces to explicit unavailable/error UI where operator confidence matters. +3. Clean old Phase 5d/e temporary worktrees in a separate explicit cleanup pass. +``` + +## Cleanup After Merge + +Only after all useful Phase 5j work is merged and pushed: + +```bash +git worktree remove .worktrees/phase5j-profile-settings +git worktree remove .worktrees/phase5j-api-token-settings +git worktree remove .worktrees/phase5j-security-settings +git worktree remove .worktrees/phase5j-settings-index +git worktree remove .worktrees/phase5j-settings-e2e +git worktree remove .worktrees/phase5j-package-metadata +git branch -d phase5j-profile-settings phase5j-api-token-settings phase5j-security-settings phase5j-settings-index phase5j-settings-e2e phase5j-package-metadata +``` + +Do not remove old `.worktrees/phase5d-*` or `.worktrees/phase5e-*` as part of Phase 5j unless the user explicitly asks for that cleanup. + +## Acceptance Criteria + +Phase 5j is complete when: + +- `/settings` is no longer an empty placeholder and links to all settings sections. +- `/settings/profile` renders current user identity/scope from `listUsers` with mock fallback. +- `/settings/security` renders current user security status from `listUsers` with mock fallback. +- `/settings/api-tokens` renders key status from `getUserKeys` and can generate a key pair through `registerUserKeys`, or generation is explicitly deferred with read-only status landed. +- Settings e2e tests cover active settings pages and the still-not-configured pages. +- Full web verification passes. +- `modernize-2026` is pushed. +- `/Users/damian/Claude/HANDOVER.md` points to the new checkpoint and this plan file. + +## Self-Review + +- Spec coverage: The plan covers the user-requested Phase 5j hold point, the queued settings slices, the optional package warning cleanup, merge/push, handover, and cleanup. +- Placeholder scan: The plan avoids TBD/TODO language. Optional items have explicit abort criteria and do not block completion. +- Type consistency: `users.ts` owns reusable user/listUsers types; `security-settings.ts` imports them during final merge. API-token names match CloudStack commands and response envelope casing already present in the repo. diff --git a/engine/api/pom.xml b/engine/api/pom.xml index cb50ef0cd46b..a84f7580006b 100644 --- a/engine/api/pom.xml +++ b/engine/api/pom.xml @@ -59,18 +59,18 @@ ${project.version}
- javax.xml.bind - jaxb-api + jakarta.xml.bind + jakarta.xml.bind-api ${cs.jaxb.version} - com.sun.xml.bind + org.glassfish.jaxb jaxb-core ${cs.jaxb.version} - com.sun.xml.bind - jaxb-impl + org.glassfish.jaxb + jaxb-runtime ${cs.jaxb.impl.version} diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntity.java b/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntity.java index 55fb5d8d5b78..5415f0ef22f8 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntity.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntity.java @@ -21,11 +21,11 @@ import java.util.List; import java.util.Map; -import javax.ws.rs.BeanParam; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.xml.bind.annotation.XmlRootElement; +import jakarta.ws.rs.BeanParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.xml.bind.annotation.XmlRootElement; import org.apache.cloudstack.engine.entity.api.CloudStackEntity; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceEntity.java b/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceEntity.java index bc532c30f294..c2458d9fdd2f 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceEntity.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceEntity.java @@ -18,9 +18,9 @@ */ package org.apache.cloudstack.engine.datacenter.entity.api; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Produces; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Produces; import org.apache.cloudstack.engine.entity.api.CloudStackEntity; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntity.java b/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntity.java index 6750bf11508f..9a329226aa13 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntity.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntity.java @@ -20,10 +20,10 @@ import java.util.List; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.xml.bind.annotation.XmlRootElement; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.xml.bind.annotation.XmlRootElement; import org.apache.cloudstack.engine.service.api.ProvisioningService; import org.apache.cloudstack.framework.ws.jackson.Url; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/entity/api/CloudStackEntity.java b/engine/api/src/main/java/org/apache/cloudstack/engine/entity/api/CloudStackEntity.java index 7891b90e2aa2..4e3557576268 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/entity/api/CloudStackEntity.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/entity/api/CloudStackEntity.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.ws.rs.GET; +import jakarta.ws.rs.GET; /** * All entities returned by the Cloud Orchestration Platform must implement diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ClusterRestService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ClusterRestService.java index a38b623e0cb4..bd58dadaf46c 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ClusterRestService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ClusterRestService.java @@ -20,13 +20,13 @@ import java.util.List; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.datacenter.entity.api.ClusterEntity; import org.apache.cloudstack.engine.service.api.ProvisioningService; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/NetworkRestService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/NetworkRestService.java index 8e470854456d..b9a53c16a614 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/NetworkRestService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/NetworkRestService.java @@ -20,13 +20,13 @@ import java.util.List; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.cloud.entity.api.NetworkEntity; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/PodRestService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/PodRestService.java index 9bad3c75cf33..8ea779308220 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/PodRestService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/PodRestService.java @@ -18,13 +18,13 @@ */ package org.apache.cloudstack.engine.rest.service.api; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.datacenter.entity.api.PodEntity; import org.apache.cloudstack.engine.service.api.ProvisioningService; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VirtualMachineRestService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VirtualMachineRestService.java index b731d36abcf1..24bc8824cb78 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VirtualMachineRestService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VirtualMachineRestService.java @@ -20,12 +20,12 @@ import java.util.List; -import javax.ws.rs.GET; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.cloud.entity.api.VirtualMachineEntity; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VolumeRestService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VolumeRestService.java index 1de87aecae7f..63c746c774fd 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VolumeRestService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/VolumeRestService.java @@ -20,14 +20,14 @@ import java.util.List; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.cloud.entity.api.VolumeEntity; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ZoneRestService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ZoneRestService.java index 17f59afd4bc5..2cf7ddce38a2 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ZoneRestService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/rest/service/api/ZoneRestService.java @@ -20,14 +20,14 @@ import java.util.List; -import javax.ws.rs.DELETE; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.PUT; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.datacenter.entity.api.ZoneEntity; import org.apache.cloudstack.engine.service.api.ProvisioningService; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java index 6be71b3cb250..9deeb4894d39 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java @@ -24,11 +24,11 @@ import java.util.List; import java.util.Map; -import javax.ws.rs.DELETE; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; import org.apache.cloudstack.engine.cloud.entity.api.NetworkEntity; import org.apache.cloudstack.engine.cloud.entity.api.TemplateEntity; diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeHelper.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeHelper.java index 9fcc17cfb522..779f8dd054a7 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeHelper.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/type/VolumeTypeHelper.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; public class VolumeTypeHelper { diff --git a/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java b/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java index 1c88c7df124c..166900f3bece 100644 --- a/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java +++ b/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java @@ -22,8 +22,8 @@ import java.util.HashMap; import java.util.Map; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import com.cloud.network.Network; import org.apache.commons.collections.MapUtils; diff --git a/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java b/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java index 107e177ef579..be70ed77692f 100644 --- a/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java +++ b/engine/components-api/src/main/java/com/cloud/network/NetworkStateListener.java @@ -22,7 +22,7 @@ import java.util.HashMap; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.events.EventDistributor; diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java index 792a3a6b397f..0bfe28ecd7ec 100644 --- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java @@ -31,6 +31,7 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.IpAddress; +import com.cloud.network.element.VpcProvider; import com.cloud.network.Network; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; @@ -216,4 +217,11 @@ public interface VpcManager { * Returns true if the network is part of a VPC, and the VPC is created from conserve mode enabled VPC offering */ boolean isNetworkOnVpcEnabledConserveMode(Network network); + + /** + * Returns the cached list of {@link VpcProvider} network elements (lazily initialized from + * the VPC virtual-router provider). Used by static-route, ACL and other VPC sub-services + * that need to apply state through the configured provider. + */ + List getVpcElements(); } diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java b/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java index 40e4a0f3dfc4..444a2a34abc7 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageUtil.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index 1215829d92f8..5c147610c927 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.StringUtils; diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java index 38a198b73040..9049c58a59e7 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java @@ -37,7 +37,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; diff --git a/engine/orchestration/src/main/java/com/cloud/cluster/agentlb/ClusterBasedAgentLoadBalancerPlanner.java b/engine/orchestration/src/main/java/com/cloud/cluster/agentlb/ClusterBasedAgentLoadBalancerPlanner.java index 5b05b4df0423..0da0ef665444 100644 --- a/engine/orchestration/src/main/java/com/cloud/cluster/agentlb/ClusterBasedAgentLoadBalancerPlanner.java +++ b/engine/orchestration/src/main/java/com/cloud/cluster/agentlb/ClusterBasedAgentLoadBalancerPlanner.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.cluster.ManagementServerHostVO; import org.springframework.stereotype.Component; diff --git a/engine/orchestration/src/main/java/com/cloud/vm/ClusteredVirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/ClusteredVirtualMachineManagerImpl.java index b8995f6aa4a5..d9deaba32308 100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/ClusteredVirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/ClusteredVirtualMachineManagerImpl.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.cluster.ClusterManager; diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 17ddf8706702..6cda36fa3eed 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -18,47 +18,29 @@ package com.cloud.vm; import static com.cloud.configuration.ConfigurationManagerImpl.EXPOSE_ERRORS_TO_USER; -import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; -import java.lang.reflect.Field; import java.net.URI; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; -import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; import java.util.Set; -import java.util.TimeZone; import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; -import javax.persistence.EntityExistsException; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; -import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; -import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; -import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.context.CallContext; @@ -78,14 +60,11 @@ import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao; import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO; -import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.Outcome; import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; -import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.framework.jobs.impl.JobSerializerHelper; -import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl; import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageDispatcher; @@ -94,12 +73,10 @@ import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.reservation.dao.ReservationDao; -import org.apache.cloudstack.resource.ResourceCleanupService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; -import org.apache.cloudstack.utils.cache.SingleCache; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.cloudstack.vm.UnmanagedVMsManager; @@ -119,45 +96,18 @@ import com.cloud.agent.api.ClusterVMMetaDataSyncAnswer; import com.cloud.agent.api.ClusterVMMetaDataSyncCommand; import com.cloud.agent.api.Command; -import com.cloud.agent.api.GetVmDiskStatsAnswer; -import com.cloud.agent.api.GetVmDiskStatsCommand; -import com.cloud.agent.api.GetVmNetworkStatsAnswer; -import com.cloud.agent.api.GetVmNetworkStatsCommand; -import com.cloud.agent.api.GetVmStatsAnswer; -import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.MigrateCommand; -import com.cloud.agent.api.MigrateVmToPoolAnswer; -import com.cloud.agent.api.ModifyTargetsCommand; import com.cloud.agent.api.PingRoutingCommand; -import com.cloud.agent.api.PlugNicAnswer; -import com.cloud.agent.api.PlugNicCommand; -import com.cloud.agent.api.PrepareExternalProvisioningAnswer; -import com.cloud.agent.api.PrepareExternalProvisioningCommand; import com.cloud.agent.api.PrepareForMigrationAnswer; import com.cloud.agent.api.PrepareForMigrationCommand; -import com.cloud.agent.api.RebootAnswer; import com.cloud.agent.api.RebootCommand; -import com.cloud.agent.api.RecreateCheckpointsCommand; -import com.cloud.agent.api.ReplugNicAnswer; -import com.cloud.agent.api.ReplugNicCommand; -import com.cloud.agent.api.RestoreVMSnapshotAnswer; -import com.cloud.agent.api.RestoreVMSnapshotCommand; -import com.cloud.agent.api.ScaleVmCommand; import com.cloud.agent.api.StartAnswer; import com.cloud.agent.api.StartCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; -import com.cloud.agent.api.UnPlugNicAnswer; -import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UnmanageInstanceCommand; -import com.cloud.agent.api.UnregisterVMCommand; -import com.cloud.agent.api.UpdateVmNicAnswer; -import com.cloud.agent.api.UpdateVmNicCommand; -import com.cloud.agent.api.VmDiskStatsEntry; -import com.cloud.agent.api.VmNetworkStatsEntry; -import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.DiskTO; @@ -169,19 +119,11 @@ import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.alert.AlertManager; import com.cloud.api.ApiDBUtils; -import com.cloud.api.query.dao.DomainRouterJoinDao; -import com.cloud.api.query.dao.UserVmJoinDao; -import com.cloud.api.query.vo.DomainRouterJoinVO; -import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.capacity.CapacityManager; import com.cloud.configuration.Resource; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.ClusterDetailsVO; -import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.HostPodVO; -import com.cloud.dc.Pod; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.HostPodDao; @@ -192,13 +134,9 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.deploy.DeploymentPlanningManagerImpl; -import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; import com.cloud.domain.Domain; -import com.cloud.domain.dao.DomainDao; import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; -import com.cloud.event.UsageEventUtils; -import com.cloud.event.UsageEventVO; import com.cloud.exception.AffinityConflictException; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; @@ -210,7 +148,6 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.StorageAccessException; -import com.cloud.exception.StorageUnavailableException; import com.cloud.ha.HighAvailabilityManager; import com.cloud.ha.HighAvailabilityManager.WorkType; import com.cloud.host.Host; @@ -220,44 +157,26 @@ import com.cloud.host.dao.HostDetailsDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuru; -import com.cloud.hypervisor.HypervisorGuruBase; import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.network.NetworkService; -import com.cloud.network.Networks; import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkDetailVO; import com.cloud.network.dao.NetworkDetailsDao; -import com.cloud.network.dao.NetworkVO; import com.cloud.network.router.VirtualRouter; import com.cloud.network.security.SecurityGroupManager; -import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.VpcDao; import com.cloud.offering.DiskOffering; import com.cloud.offering.DiskOfferingInfo; -import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; -import com.cloud.offerings.NetworkOfferingVO; -import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.org.Cluster; import com.cloud.resource.ResourceManager; -import com.cloud.resource.ResourceState; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; -import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; import com.cloud.storage.Snapshot; -import com.cloud.storage.Storage; -import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; -import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.VMTemplateZoneVO; import com.cloud.storage.Volume; -import com.cloud.storage.Volume.Type; -import com.cloud.storage.VolumeApiService; -import com.cloud.storage.VolumeApiServiceImpl; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; import com.cloud.storage.dao.GuestOSCategoryDao; @@ -271,15 +190,12 @@ import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; import com.cloud.user.User; -import com.cloud.user.dao.AccountDao; import com.cloud.uservm.UserVm; import com.cloud.utils.DateUtil; import com.cloud.utils.Journal; import com.cloud.utils.LogUtils; import com.cloud.utils.Pair; -import com.cloud.utils.Predicate; import com.cloud.utils.ReflectionUse; -import com.cloud.utils.StringUtils; import com.cloud.utils.Ternary; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; @@ -289,8 +205,6 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionCallbackWithException; -import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; -import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.TransactionStatus; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.ExecutionException; @@ -298,18 +212,16 @@ import com.cloud.utils.fsm.StateMachine2; import com.cloud.vm.ItWorkVO.Step; import com.cloud.vm.VirtualMachine.Event; -import com.cloud.vm.VirtualMachine.PowerState; import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.snapshot.VMSnapshotManager; -import com.cloud.vm.snapshot.VMSnapshotVO; -import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.google.gson.Gson; -public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { +public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable, VmStateMachineActions, + VmNetworkAttachmentOrchestrationService.BackendNicOperations { public static final String VM_WORK_JOB_HANDLER = VirtualMachineManagerImpl.class.getSimpleName(); @@ -376,8 +288,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private StoragePoolHostDao _poolHostDao; @Inject - private VMSnapshotDao _vmSnapshotDao; - @Inject private AffinityGroupVMMapDao _affinityGroupVMMapDao; @Inject private EntityManager _entityMgr; @@ -396,8 +306,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private VMSnapshotManager _vmSnapshotMgr; @Inject - private ClusterDetailsDao _clusterDetailsDao; - @Inject private VMInstanceDetailsDao vmInstanceDetailsDao; @Inject private VolumeOrchestrationService volumeMgr; @@ -420,36 +328,14 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private SecurityGroupManager _securityGroupManager; @Inject - private UserVmDeployAsIsDetailsDao userVmDeployAsIsDetailsDao; - @Inject - private UserVmJoinDao userVmJoinDao; - @Inject - private NetworkOfferingDao networkOfferingDao; - @Inject - private DomainRouterJoinDao domainRouterJoinDao; - @Inject - private AnnotationDao annotationDao; - @Inject - private AccountDao accountDao; - @Inject - private VpcDao vpcDao; - @Inject - private DomainDao domainDao; - @Inject public NetworkService networkService; @Inject - ResourceCleanupService resourceCleanupService; - @Inject VmWorkJobDao vmWorkJobDao; @Inject DataStoreProviderManager dataStoreProviderManager; @Inject - BackupManager backupManager; - @Inject BackupDao backupDao; - private SingleCache> vmIdsInProgressCache; - @Inject private SnapshotDataStoreDao snapshotDataStoreDao; @@ -462,6 +348,62 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac ExtensionsManager extensionsManager; @Inject ExtensionDetailsDao extensionDetailsDao; + @Inject + private VmServiceOfferingUpgradeManager vmServiceOfferingUpgradeManager; + @Inject + private VmIscsiTargetManager vmIscsiTargetManager; + @Inject + private VmStatsCollector vmStatsCollector; + @Inject + protected VmExternalProvisioningManager vmExternalProvisioningManager; + @Inject + protected VmVolumeMigrationPlanningService vmVolumeMigrationPlanningService; + @Inject + protected VmVolumeMigrationPlanningServiceImpl vmVolumeMigrationPlanningServiceImpl; + @Inject + protected VmOfflineStorageMigrationService vmOfflineStorageMigrationService; + @Inject + protected VmOfflineStorageMigrationServiceImpl vmOfflineStorageMigrationServiceImpl; + @Inject + protected VmDiskOfferingSuitabilityService vmDiskOfferingSuitabilityService; + @Inject + protected VmCommandSpecPostProcessingService vmCommandSpecPostProcessingService; + @Inject + protected VmWorkJobQueueService vmWorkJobQueueService; + @Inject + protected VmExpungeOrchestrationService vmExpungeOrchestrationService; + @Inject + protected VmDestroyOrchestrationService vmDestroyOrchestrationService; + @Inject + protected VmMetadataSyncService vmMetadataSyncService; + @Inject + protected VmNetworkNameMappingService vmNetworkNameMappingService; + @Inject + protected VmStartProfilePreparationService vmStartProfilePreparationService; + @Inject + protected VmVlanPersistenceMappingService vmVlanPersistenceMappingService; + @Inject + protected VmStopCommandService vmStopCommandService; + @Inject + protected VmStopOrchestrationService vmStopOrchestrationService; + @Inject + protected VmMigrationCheckpointService vmMigrationCheckpointService; + @Inject + protected VmRebootOrchestrationService vmRebootOrchestrationService; + @Inject + protected VmPowerStateSyncManager vmPowerStateSyncManager; + @Inject + protected VmNicUpdateService vmNicUpdateService; + @Inject + protected VmAllocationOrchestrationService vmAllocationOrchestrationService; + @Inject + protected VmNicBackendCommandService vmNicBackendCommandService; + @Inject + protected VmMigrateAwayPlanningService vmMigrateAwayPlanningService; + @Inject + protected VmScaleReconfigurationService vmScaleReconfigurationService; + @Inject + protected VmNetworkAttachmentOrchestrationService vmNetworkAttachmentOrchestrationService; VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); @@ -507,8 +449,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac Long.class, "systemvm.root.disk.size", "-1", "Size of root volume (in GB) of system VMs and virtual routers", true); - private boolean syncTransitioningVmPowerState; - ScheduledExecutorService _executor = null; private long _nodeId; @@ -539,114 +479,22 @@ public void allocate(final String vmInstanceName, final VirtualMachineTemplate t final LinkedHashMap> auxiliaryNetworks,final DeploymentPlan plan, final HypervisorType hyperType, final Map> extraDhcpOptions, final Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { - - logger.info("Allocating Instance from Template: {} with hostname: {} and {} networks", template, vmInstanceName, auxiliaryNetworks.size()); - VMInstanceVO persistedVm = null; - try { - final VMInstanceVO vm = _vmDao.findVMByInstanceName(vmInstanceName); - final Account owner = _entityMgr.findById(Account.class, vm.getAccountId()); - - logger.debug("Allocating entries for VM: " + vm); - - vm.setDataCenterId(plan.getDataCenterId()); - if (plan.getPodId() != null) { - vm.setPodIdToDeployIn(plan.getPodId()); - } - assert plan.getClusterId() == null && plan.getPoolId() == null : "We currently don't support cluster and pool preset yet"; - persistedVm = _vmDao.persist(vm); - - final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(persistedVm, template, serviceOffering, null, null); - - Long rootDiskSize = rootDiskOfferingInfo.getSize(); - if (vm.getType().isUsedBySystem() && SystemVmRootDiskSize.value() != null && SystemVmRootDiskSize.value() > 0L) { - rootDiskSize = SystemVmRootDiskSize.value(); - } - final Long rootDiskSizeFinal = rootDiskSize; - - logger.debug("Allocating NICs for {}", persistedVm); - - try { - if (!vmProfile.getBootArgs().contains("ExternalLoadBalancerVm")) { - _networkMgr.allocate(vmProfile, auxiliaryNetworks, extraDhcpOptions); - } - } catch (final ConcurrentOperationException e) { - throw new CloudRuntimeException("Concurrent operation while trying to allocate resources for the VM", e); - } - - logger.debug("Allocating disks for {}", persistedVm); - - allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot); - - // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. - CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); - try { - if (dataDiskOfferings != null) { - int index = 0; - for (final DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { - Long deviceId = dataDiskDeviceIds.get(index++); - String volumeName = deviceId == null ? "DATA-" + persistedVm.getId() : "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId); - volumeMgr.allocateRawVolume(Type.DATADISK, volumeName, dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), - dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, true); - } - } - if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { - Long diskNumber = 1L; - for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { - DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); - long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); - VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf( diskNumber), diskOffering, diskOfferingSize, null, null, - persistedVm, dataDiskTemplate, owner, diskNumber, true); - diskNumber++; - } - } - } finally { - // Remove volumeContext and pop vmContext back - CallContext.unregister(); - } - - logger.debug("Allocation completed for VM: " + persistedVm); - } catch (InsufficientCapacityException | CloudRuntimeException e) { - // Failed VM will be in Stopped. Transition it to Error, so it can be expunged by ExpungeTask or similar - try { - if (persistedVm != null) { - stateTransitTo(persistedVm, VirtualMachine.Event.OperationFailedToError, null); - } - } catch (NoTransitionException nte) { - logger.error("Failed to transition {} in {} state to Error state", persistedVm, persistedVm.getState().toString()); - } - throw e; - } + vmAllocationOrchestrationService.allocate(vmInstanceName, template, serviceOffering, rootDiskOfferingInfo, dataDiskOfferings, + dataDiskDeviceIds, auxiliaryNetworks, plan, hyperType, extraDhcpOptions, datadiskTemplateToDiskOfferingMap, volume, snapshot); } - private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, Account owner, Long rootDiskSizeFinal, Volume volume, Snapshot snapshot) { - // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. - CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); - try { - String rootVolumeName = String.format("ROOT-%s", vm.getId()); - if (template.getFormat() == ImageFormat.ISO) { - volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(), - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, true); - } else if (Arrays.asList(ImageFormat.BAREMETAL, ImageFormat.EXTERNAL).contains(template.getFormat())) { - logger.debug("{} has format [{}]. Skipping ROOT volume [{}] allocation.", template, template.getFormat(), rootVolumeName); - } else { - volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal, - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, volume, snapshot); - } - } finally { - // Remove volumeContext and pop vmContext back - CallContext.unregister(); - } + protected void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, Account owner, Long rootDiskSizeFinal, Volume volume, Snapshot snapshot) { + vmAllocationOrchestrationService.allocateRootVolume(vm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot); } @Override public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { - DiskOffering diskOffering = _diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); - allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), new ArrayList<>(), networks, plan, hyperType, null, null, volume, snapshot); + vmAllocationOrchestrationService.allocate(vmInstanceName, template, serviceOffering, networks, plan, hyperType, volume, snapshot); } - VirtualMachineGuru getVmGuru(final VirtualMachine vm) { + @Override + public VirtualMachineGuru getVmGuru(final VirtualMachine vm) { if(vm != null) { return _vmGurus.get(vm.getType()); } @@ -655,238 +503,20 @@ VirtualMachineGuru getVmGuru(final VirtualMachine vm) { @Override public void expunge(final String vmUuid) throws ResourceUnavailableException { - try { - advanceExpunge(vmUuid); - } catch (final OperationTimedoutException e) { - throw new CloudRuntimeException("Operation timed out", e); - } catch (final ConcurrentOperationException e) { - throw new CloudRuntimeException("Concurrent operation ", e); - } + vmExpungeOrchestrationService.expunge(vmUuid); } @Override public void advanceExpunge(final String vmUuid) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - advanceExpunge(vm); - } - - private boolean isValidSystemVMType(VirtualMachine vm) { - return VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType()) || - VirtualMachine.Type.ConsoleProxy.equals(vm.getType()); - } - - private boolean isVmDestroyed(VMInstanceVO vm) { - if (vm == null || vm.getRemoved() != null) { - logger.debug("Unable to find vm or vm is expunged: " + vm); - return true; - } - return false; + vmExpungeOrchestrationService.advanceExpunge(vmUuid); } protected void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException { - if (isVmDestroyed(vm)) { - return; - } - - if (HypervisorType.External.equals(vm.getHypervisorType())) { - UserVmVO userVM = _userVmDao.findById(vm.getId()); - _userVmDao.loadDetails(userVM); - userVM.setDetail(VmDetailConstants.EXPUNGE_EXTERNAL_VM, Boolean.TRUE.toString()); - _userVmDao.saveDetails(userVM); - } - - advanceStop(vm.getUuid(), VmDestroyForcestop.value()); - vm = _vmDao.findByUuid(vm.getUuid()); - - try { - if (!stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, vm.getHostId())) { - logger.debug("Unable to expunge the vm because it is not in the correct state: " + vm); - throw new CloudRuntimeException("Unable to expunge " + vm); - - } - } catch (final NoTransitionException e) { - logger.debug("Unable to expunge the vm because it is not in the correct state: " + vm); - throw new CloudRuntimeException("Unable to expunge " + vm, e); - } - - logger.debug("Expunging vm " + vm); - - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType()); - - List vmNics = profile.getNics(); - logger.debug("Cleaning up NICS [{}] of {}.", vmNics.stream().map(nic -> nic.toString()).collect(Collectors.joining(", ")),vm.toString()); - final List nicExpungeCommands = hvGuru.finalizeExpungeNics(vm, profile.getNics()); - _networkMgr.cleanupNics(profile); - - logger.debug("Cleaning up hypervisor data structures (ex. SRs in XenServer) for managed storage. Data from {}.", vm.toString()); - - final List volumeExpungeCommands = hvGuru.finalizeExpungeVolumes(vm); - - final Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); - - List> targets = getTargets(hostId, vm.getId()); - - if (CollectionUtils.isNotEmpty(volumeExpungeCommands) && hostId != null) { - final Commands cmds = new Commands(Command.OnError.Stop); - - for (final Command volumeExpungeCommand : volumeExpungeCommands) { - volumeExpungeCommand.setBypassHostMaintenance(isValidSystemVMType(vm)); - cmds.addCommand(volumeExpungeCommand); - } - - _agentMgr.send(hostId, cmds); - handleUnsuccessfulCommands(cmds, vm); - } - - if (hostId != null) { - volumeMgr.revokeAccess(vm.getId(), hostId); - } - - volumeMgr.cleanupVolumes(vm.getId()); - - if (hostId != null && CollectionUtils.isNotEmpty(targets)) { - removeDynamicTargets(hostId, targets); - } - - final VirtualMachineGuru guru = getVmGuru(vm); - guru.finalizeExpunge(vm); - - userVmDeployAsIsDetailsDao.removeDetails(vm.getId()); - - // Remove comments (if any) - annotationDao.removeByEntityType(AnnotationService.EntityType.VM.name(), vm.getUuid()); - - // send hypervisor-dependent commands before removing - final List finalizeExpungeCommands = hvGuru.finalizeExpunge(vm); - handleUnsuccessfulExpungeOperation(finalizeExpungeCommands, nicExpungeCommands, vm, hostId); - - if (logger.isDebugEnabled()) { - logger.debug("Expunged " + vm); - } - resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm); - } - - private void handleUnsuccessfulExpungeOperation(List finalizeExpungeCommands, List nicExpungeCommands, - VMInstanceVO vm, Long hostId) throws OperationTimedoutException, AgentUnavailableException { - if ((CollectionUtils.isNotEmpty(finalizeExpungeCommands) || CollectionUtils.isNotEmpty(nicExpungeCommands)) && hostId != null) { - final Commands cmds = new Commands(Command.OnError.Stop); - addAllExpungeCommandsFromList(finalizeExpungeCommands, cmds, vm); - addAllExpungeCommandsFromList(nicExpungeCommands, cmds, vm); - _agentMgr.send(hostId, cmds); - if (!cmds.isSuccessful()) { - for (final Answer answer : cmds.getAnswers()) { - if (!answer.getResult()) { - logger.warn("Failed to expunge vm due to: {}", answer.getDetails()); - throw new CloudRuntimeException(String.format("Unable to expunge %s due to %s", vm, answer.getDetails())); - } - } - } - } - } - - protected void handleUnsuccessfulCommands(Commands cmds, VMInstanceVO vm) throws CloudRuntimeException { - String cmdsStr = cmds.toString(); - String vmToString = vm.toString(); - - if (cmds.isSuccessful()) { - logger.debug("The commands [{}] to {} were successful.", cmdsStr, vmToString); - return; - } - - logger.info("The commands [{}] to {} were unsuccessful. Handling answers.", cmdsStr, vmToString); - - Answer[] answers = cmds.getAnswers(); - if (answers == null) { - logger.debug("There are no answers to commands [{}] to {}.", cmdsStr, vmToString); - return; - } - - for (Answer answer : answers) { - String details = answer.getDetails(); - if (!answer.getResult()) { - String message = String.format("Unable to expunge %s due to [%s].", vmToString, details); - logger.error(message); - throw new CloudRuntimeException(message); - } - - logger.debug("Commands [{}] to {} got answer [{}].", cmdsStr, vmToString, details); - } - } - - private void addAllExpungeCommandsFromList(List cmdList, Commands cmds, VMInstanceVO vm) { - if (CollectionUtils.isEmpty(cmdList)) { - return; - } - for (final Command command : cmdList) { - command.setBypassHostMaintenance(isValidSystemVMType(vm)); - logger.trace("Adding expunge command [{}] for VM [{}]", command.toString(), vm.toString()); - cmds.addCommand(command); - } - } - - private List> getTargets(Long hostId, long vmId) { - List> targets = new ArrayList<>(); - - HostVO hostVO = _hostDao.findById(hostId); - - if (hostVO == null || hostVO.getHypervisorType() != HypervisorType.VMware) { - return targets; - } - - List volumes = _volsDao.findByInstance(vmId); - - if (CollectionUtils.isEmpty(volumes)) { - return targets; - } - - for (VolumeVO volume : volumes) { - StoragePoolVO storagePoolVO = _storagePoolDao.findById(volume.getPoolId()); - - if (storagePoolVO != null && storagePoolVO.isManaged()) { - Map target = new HashMap<>(); - - target.put(ModifyTargetsCommand.STORAGE_HOST, storagePoolVO.getHostAddress()); - target.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePoolVO.getPort())); - target.put(ModifyTargetsCommand.IQN, volume.get_iScsiName()); - - targets.add(target); - } - } - - return targets; - } - - private void removeDynamicTargets(long hostId, List> targets) { - ModifyTargetsCommand cmd = new ModifyTargetsCommand(); - - cmd.setTargets(targets); - cmd.setApplyToAllHostsInCluster(true); - cmd.setAdd(false); - cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC); - - sendModifyTargetsCommand(cmd, hostId); - } - - private void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) { - Answer answer = _agentMgr.easySend(hostId, cmd); - - if (answer == null) { - logger.warn("Unable to get an answer to the modify targets command. Targets [{}].", - () -> cmd.getTargets().stream().map(target -> target.toString()).collect(Collectors.joining(", "))); - return; - } - - if (!answer.getResult()) { - logger.warn("Unable to modify targets [{}] on the host [{}].", - () -> cmd.getTargets().stream().map(target -> target.toString()).collect(Collectors.joining(", ")), () -> hostId); - } + vmExpungeOrchestrationService.advanceExpunge(vm); } @Override public boolean start() { - vmIdsInProgressCache = new SingleCache<>(10, vmWorkJobDao::listVmIdsWithPendingJob); _executor.scheduleAtFixedRate(new CleanupTask(), 5, VmJobStateReportInterval.value(), TimeUnit.SECONDS); _executor.scheduleAtFixedRate(new TransitionTask(), VmOpCleanupInterval.value(), VmOpCleanupInterval.value(), TimeUnit.SECONDS); cancelWorkItems(_nodeId); @@ -914,8 +544,6 @@ public boolean configure(final String name, final Map xmlParams) _messageBus.subscribe(VirtualMachineManager.Topics.VM_POWER_STATE, MessageDispatcher.getDispatcher(this)); - syncTransitioningVmPowerState = Boolean.TRUE.equals(VmSyncPowerStateTransitioning.value()); - return true; } @@ -1103,24 +731,22 @@ public void advanceStart(final String vmUuid, final Map":params.get(VirtualMachineProfile.Param.BootIntoSetup))); final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); try { orchestrateStart(vmUuid, params, planToDeploy, planner); } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } } else { logger.debug("start parameter value of {} == {} during processing of queued job", VirtualMachineProfile.Param.BootIntoSetup.getName(), (params == null?"":params.get(VirtualMachineProfile.Param.BootIntoSetup))); - final Outcome outcome = startVmThroughJobQueue(vmUuid, params, planToDeploy, planner); + final Outcome outcome = vmWorkJobQueueService.startVmThroughJobQueue(vmUuid, params, planToDeploy, planner); - retrieveVmFromJobOutcome(outcome, vmUuid, "startVm"); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "startVm"); - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); } } @@ -1148,22 +774,7 @@ protected void addHostIpToCertDetailsIfConfigAllows(Host vmHost, Map existingRootVolumes = _volsDao.findReadyRootVolumesByInstance(vm.getId()); - if (CollectionUtils.isNotEmpty(existingRootVolumes)) { - return; - } - final VMTemplateVO template = _templateDao.findById(vm.getTemplateId()); - if (template == null) { - String msg = "Template for the VM instance can not be found, VM instance configuration needs to be updated"; - logger.error("{}. Template ID: {} seems to be removed", msg, vm.getTemplateId()); - throw new CloudRuntimeException(msg); - } - final VMTemplateZoneVO templateZoneVO = templateZoneDao.findByZoneTemplate(vm.getDataCenterId(), template.getId()); - if (templateZoneVO == null) { - String msg = "Template for the VM instance can not be found in the zone ID: %s, VM instance configuration needs to be updated"; - logger.error("{}. {}", msg, template); - throw new CloudRuntimeException(msg); - } + vmAllocationOrchestrationService.checkIfTemplateNeededForCreatingVmVolumes(vm); } protected void checkAndAttemptMigrateVmAcrossCluster(final VMInstanceVO vm, final Long destinationClusterId, final Map volumePoolMap) { @@ -1191,96 +802,24 @@ protected void checkAndAttemptMigrateVmAcrossCluster(final VMInstanceVO vm, fina } protected void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMInstanceVO vm) { - String metadataManufacturer = VmMetadataManufacturer.valueIn(vm.getDataCenterId()); - if (StringUtils.isBlank(metadataManufacturer)) { - metadataManufacturer = VmMetadataManufacturer.defaultValue(); - } - vmTO.setMetadataManufacturer(metadataManufacturer); - String metadataProduct = VmMetadataProductName.valueIn(vm.getDataCenterId()); - if (StringUtils.isBlank(metadataProduct)) { - metadataProduct = String.format("CloudStack %s Hypervisor", vm.getHypervisorType().toString()); - } - vmTO.setMetadataProductName(metadataProduct); + vmExternalProvisioningManager.updateVmMetadataManufacturerAndProduct(vmTO, vm); } protected void updateExternalVmDetailsFromPrepareAnswer(VirtualMachineTO vmTO, UserVmVO userVmVO, Map newDetails) { - if (newDetails == null || newDetails.equals(vmTO.getDetails())) { - return; - } - vmTO.setDetails(newDetails); - userVmVO.setDetails(newDetails); - _userVmDao.saveDetails(userVmVO); + vmExternalProvisioningManager.updateExternalVmDetailsFromPrepareAnswer(vmTO, userVmVO, newDetails); } protected void updateExternalVmDataFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { - final String vncPassword = updatedTO.getVncPassword(); - final Map details = updatedTO.getDetails(); - if ((vncPassword == null || vncPassword.equals(vmTO.getVncPassword())) && - (details == null || details.equals(vmTO.getDetails()))) { - return; - } - UserVmVO userVmVO = _userVmDao.findById(vmTO.getId()); - if (userVmVO == null) { - return; - } - if (vncPassword != null && !vncPassword.equals(userVmVO.getPassword())) { - userVmVO.setVncPassword(vncPassword); - vmTO.setVncPassword(vncPassword); - } - updateExternalVmDetailsFromPrepareAnswer(vmTO, userVmVO, updatedTO.getDetails()); + vmExternalProvisioningManager.updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); } protected void updateExternalVmNicsFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { - if (ObjectUtils.anyNull(vmTO.getNics(), updatedTO.getNics())) { - return; - } - Map originalNicsByUuid = new HashMap<>(); - for (NicTO nic : vmTO.getNics()) { - originalNicsByUuid.put(nic.getNicUuid(), nic); - } - for (NicTO updatedNicTO : updatedTO.getNics()) { - final String nicUuid = updatedNicTO.getNicUuid(); - NicTO originalNicTO = originalNicsByUuid.get(nicUuid); - if (originalNicTO == null) { - continue; - } - final String mac = updatedNicTO.getMac(); - final String ip4 = updatedNicTO.getIp(); - final String ip6 = updatedNicTO.getIp6Address(); - if (Objects.equals(mac, originalNicTO.getMac()) && - Objects.equals(ip4, originalNicTO.getIp()) && - Objects.equals(ip6, originalNicTO.getIp6Address())) { - continue; - } - NicVO nicVO = _nicsDao.findByUuid(nicUuid); - if (nicVO == null) { - continue; - } - logger.debug("Updating {} during External VM preparation", nicVO); - if (ip4 != null && !ip4.equals(nicVO.getIPv4Address())) { - nicVO.setIPv4Address(ip4); - originalNicTO.setIp(ip4); - } - if (ip6 != null && !ip6.equals(nicVO.getIPv6Address())) { - nicVO.setIPv6Address(ip6); - originalNicTO.setIp6Address(ip6); - } - if (mac != null && !mac.equals(nicVO.getMacAddress())) { - nicVO.setMacAddress(mac); - originalNicTO.setMac(mac); - } - _nicsDao.update(nicVO.getId(), nicVO); - } + vmExternalProvisioningManager.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); } protected void updateExternalVmFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { - if (updatedTO == null) { - return; - } - updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); - updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); - return; + vmExternalProvisioningManager.updateExternalVmFromPrepareAnswer(vmTO, updatedTO); } protected void processPrepareExternalProvisioning(boolean firstStart, Host host, @@ -1297,45 +836,7 @@ protected void processPrepareExternalProvisioning(boolean firstStart, Host host, } logger.debug("Sending PrepareExternalProvisioningCommand for {}", vmProfile); VirtualMachineTO virtualMachineTO = toVmTO(vmProfile); - if (virtualMachineTO.getNics() == null || virtualMachineTO.getNics().length == 0) { - List nics = _nicsDao.listByVmId(vmProfile.getId()); - NicTO[] nicTOs = new NicTO[nics.size()]; - nics.forEach(nicVO -> { - NicTO nicTO = toNicTO(_networkModel.getNicProfile(vmProfile.getVirtualMachine(), nicVO, dataCenter), - HypervisorType.External); - nicTOs[nicTO.getDeviceId()] = nicTO; - }); - virtualMachineTO.setNics(nicTOs); - } - Map vmDetails = virtualMachineTO.getExternalDetails(); - Map> externalDetails = extensionsManager.getExternalAccessDetails(host, - vmDetails); - PrepareExternalProvisioningCommand cmd = new PrepareExternalProvisioningCommand(virtualMachineTO); - cmd.setExternalDetails(externalDetails); - Answer answer = null; - CloudRuntimeException cre = new CloudRuntimeException("Failed to prepare VM"); - try { - answer = _agentMgr.send(host.getId(), cmd); - } catch (AgentUnavailableException | OperationTimedoutException e) { - logger.error("Failed PrepareExternalProvisioningCommand due to : {}", e.getMessage(), e); - throw cre; - } - if (answer == null) { - logger.error("Invalid answer received for PrepareExternalProvisioningCommand"); - throw cre; - } - if (!(answer instanceof PrepareExternalProvisioningAnswer)) { - logger.error("Unexpected answer received for PrepareExternalProvisioningCommand: [result: {}, details: {}]", - answer.getResult(), answer.getDetails()); - throw cre; - } - PrepareExternalProvisioningAnswer prepareAnswer = (PrepareExternalProvisioningAnswer)answer; - if (!prepareAnswer.getResult()) { - logger.error("Unexpected answer received for PrepareExternalProvisioningCommand: [result: {}, details: {}]", - answer.getResult(), answer.getDetails()); - throw cre; - } - updateExternalVmFromPrepareAnswer(virtualMachineTO, prepareAnswer.getVirtualMachineTO()); + vmExternalProvisioningManager.processPrepareExternalProvisioning(firstStart, host, vmProfile, dataCenter, virtualMachineTO); } @Override @@ -1533,9 +1034,9 @@ public void orchestrateStart(final String vmUuid, final Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - stopCmd.setVlanToPersistenceMap(vlanToPersistenceMap); - } + vmStopCommandService.decorateStopCommandWithNetworkDetails(stopCmd, vm); final StopCommand cmd = stopCmd; final Answer answer = _agentMgr.easySend(destHostId, cmd); if (answer != null && answer instanceof StopAnswer) { @@ -1735,18 +1232,7 @@ private boolean canExposeError(Account account) { } protected void updateStartCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, StartCommand command) { - if (!HypervisorType.External.equals(host.getHypervisorType())) { - return; - } - Map vmExternalDetails = vmTO.getExternalDetails(); - for (NicTO nic : vmTO.getNics()) { - if (!nic.isDefaultNic()) { - continue; - } - vmExternalDetails.put(VmDetailConstants.CLOUDSTACK_VLAN, networkService.getNicVlanValueForExternalVm(nic)); - } - Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmExternalDetails); - command.setExternalDetails(externalDetails); + vmExternalProvisioningManager.updateStartCommandWithExternalDetails(host, vmTO, command); } protected void updateStopCommandForExternalHypervisorType(final HypervisorType hypervisorType, @@ -1754,282 +1240,46 @@ protected void updateStopCommandForExternalHypervisorType(final HypervisorType h if (!HypervisorType.External.equals(hypervisorType) || vmProfile.getHostId() == null) { return; } - Host host = _hostDao.findById(vmProfile.getHostId()); - if (host == null) { - return; - } VirtualMachineTO vmTO = ObjectUtils.defaultIfNull(stopCommand.getVirtualMachine(), toVmTO(vmProfile)); - if (MapUtils.isEmpty(vmTO.getGuestOsDetails())) { - vmTO.setGuestOsDetails(null); - } - if (MapUtils.isEmpty(vmTO.getExtraConfig())) { - vmTO.setExtraConfig(null); - } - if (MapUtils.isEmpty(vmTO.getNetworkIdToNetworkNameMap())) { - vmTO.setNetworkIdToNetworkNameMap(null); - } - Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmTO.getExternalDetails()); - stopCommand.setVirtualMachine(vmTO); - stopCommand.setExternalDetails(externalDetails); + vmExternalProvisioningManager.updateStopCommandForExternalHypervisorType(hypervisorType, vmProfile, stopCommand, vmTO); } protected void updateRebootCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, RebootCommand rebootCmd) { - if (!HypervisorType.External.equals(host.getHypervisorType())) { - return; - } - Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmTO.getExternalDetails()); - rebootCmd.setExternalDetails(externalDetails); + vmExternalProvisioningManager.updateRebootCommandWithExternalDetails(host, vmTO, rebootCmd); } public void setVmNetworkDetails(VMInstanceVO vm, VirtualMachineTO vmTO) { - Map networkToNetworkNameMap = new HashMap<>(); - if (VirtualMachine.Type.User.equals(vm.getType())) { - List userVmJoinVOs = userVmJoinDao.searchByIds(vm.getId()); - if (userVmJoinVOs != null && !userVmJoinVOs.isEmpty()) { - for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) { - addToNetworkNameMap(userVmJoinVO.getNetworkId(), vm.getDataCenterId(), networkToNetworkNameMap); - } - vmTO.setNetworkIdToNetworkNameMap(networkToNetworkNameMap); - } - } else if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { - List routerJoinVO = domainRouterJoinDao.getRouterByIdAndTrafficType(vm.getId(), Networks.TrafficType.Guest); - for (DomainRouterJoinVO router : routerJoinVO) { - NetworkVO guestNetwork = _networkDao.findById(router.getNetworkId()); - if (guestNetwork.getVpcId() == null && guestNetwork.getBroadcastDomainType() == Networks.BroadcastDomainType.NSX) { - addToNetworkNameMap(router.getNetworkId(), vm.getDataCenterId(), networkToNetworkNameMap); - } - } - vmTO.setNetworkIdToNetworkNameMap(networkToNetworkNameMap); - } + vmNetworkNameMappingService.setVmNetworkDetails(vm, vmTO); } - private void addToNetworkNameMap(long networkId, long dataCenterId, Map networkToNetworkNameMap) { - NetworkVO networkVO = _networkDao.findById(networkId); - Account acc = accountDao.findById(networkVO.getAccountId()); - Domain domain = domainDao.findById(networkVO.getDomainId()); - DataCenter zone = _dcDao.findById(dataCenterId); - if (Objects.isNull(zone)) { - throw new CloudRuntimeException(String.format("Failed to find zone with ID: %s", dataCenterId)); - } - if (Objects.isNull(acc)) { - throw new CloudRuntimeException(String.format("Failed to find account with ID: %s", networkVO.getAccountId())); - } - if (Objects.isNull(domain)) { - throw new CloudRuntimeException(String.format("Failed to find domain with ID: %s", networkVO.getDomainId())); - } - String networkName = String.format("D%s-A%s-Z%s", domain.getId(), acc.getId(), zone.getId()); - if (Objects.isNull(networkVO.getVpcId())) { - networkName += "-S" + networkVO.getId(); - } else { - VpcVO vpc = vpcDao.findById(networkVO.getVpcId()); - if (Objects.isNull(vpc)) { - throw new CloudRuntimeException(String.format("Failed to find VPC with ID: %s", networkVO.getVpcId())); - } - networkName = String.format("%s-V%s-S%s", networkName, vpc.getId(), networkVO.getId()); - } - networkToNetworkNameMap.put(networkVO.getId(), networkName); + protected void updateOverCommitRatioForVmProfile(VirtualMachineProfile vmProfile, long clusterId) { + vmStartProfilePreparationService.updateOverCommitRatioForVmProfile(vmProfile, clusterId); } - private void updateOverCommitRatioForVmProfile(VirtualMachineProfile vmProfile, long clusterId) { - final ClusterDetailsVO clusterDetailCpu = _clusterDetailsDao.findDetail(clusterId, VmDetailConstants.CPU_OVER_COMMIT_RATIO); - final ClusterDetailsVO clusterDetailRam = _clusterDetailsDao.findDetail(clusterId, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); - final float parsedClusterCpuDetailCpu = Float.parseFloat(clusterDetailCpu.getValue()); - final float parsedClusterDetailRam = Float.parseFloat(clusterDetailRam.getValue()); - VMInstanceDetailVO vmDetailCpu = vmInstanceDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO); - VMInstanceDetailVO vmDetailRam = vmInstanceDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); - - if ((vmDetailCpu == null && parsedClusterCpuDetailCpu > 1f) || - (vmDetailCpu != null && Float.parseFloat(vmDetailCpu.getValue()) != parsedClusterCpuDetailCpu)) { - vmInstanceDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO, clusterDetailCpu.getValue(), true); - } - if ((vmDetailRam == null && parsedClusterDetailRam > 1f) || - (vmDetailRam != null && Float.parseFloat(vmDetailRam.getValue()) != parsedClusterDetailRam)) { - vmInstanceDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, clusterDetailRam.getValue(), true); - } + protected void conditionallySetPodToDeployIn(VMInstanceVO vm) { + vmStartProfilePreparationService.conditionallySetPodToDeployIn(vm); + } - vmProfile.setCpuOvercommitRatio(Float.parseFloat(clusterDetailCpu.getValue())); - vmProfile.setMemoryOvercommitRatio(Float.parseFloat(clusterDetailRam.getValue())); + boolean areAllVolumesAllocated(long vmId) { + return vmStartProfilePreparationService.areAllVolumesAllocated(vmId); } - /** - * Setting pod id to null can result in migration of Volumes across pods. This is not desirable for VMs which - * have a volume in Ready state (happens when a VM is shutdown and started again). - * So, we set it to null only when - * migration of VM across cluster is enabled - * Or, volumes are still in allocated state for that VM (happens when VM is Starting/deployed for the first time) - */ - private void conditionallySetPodToDeployIn(VMInstanceVO vm) { - if (MIGRATE_VM_ACROSS_CLUSTERS.valueIn(vm.getDataCenterId()) || areAllVolumesAllocated(vm.getId())) { - vm.setPodIdToDeployIn(null); - } + protected void logBootModeParameters(Map params) { + vmStartProfilePreparationService.logBootModeParameters(params); } - boolean areAllVolumesAllocated(long vmId) { - final List vols = _volsDao.findByInstance(vmId); - return CollectionUtils.isEmpty(vols) || vols.stream().allMatch(v -> Volume.State.Allocated.equals(v.getState())); + protected void resetVmNicsDeviceId(Long vmId) { + vmStartProfilePreparationService.resetVmNicsDeviceId(vmId); } - private void logBootModeParameters(Map params) { - if (params == null) { - return; - } - - StringBuilder msgBuf = new StringBuilder("Uefi params "); - boolean log = false; - if (params.get(VirtualMachineProfile.Param.UefiFlag) != null) { - msgBuf.append(String.format("UefiFlag: %s ", params.get(VirtualMachineProfile.Param.UefiFlag))); - log = true; - } - if (params.get(VirtualMachineProfile.Param.BootType) != null) { - msgBuf.append(String.format("Boot Type: %s ", params.get(VirtualMachineProfile.Param.BootType))); - log = true; - } - if (params.get(VirtualMachineProfile.Param.BootMode) != null) { - msgBuf.append(String.format("Boot Mode: %s ", params.get(VirtualMachineProfile.Param.BootMode))); - log = true; - } - if (params.get(VirtualMachineProfile.Param.BootIntoSetup) != null) { - msgBuf.append(String.format("Boot into Setup: %s ", params.get(VirtualMachineProfile.Param.BootIntoSetup))); - log = true; - } - if (params.get(VirtualMachineProfile.Param.ConsiderLastHost) != null) { - msgBuf.append(String.format("Consider last host: %s ", params.get(VirtualMachineProfile.Param.ConsiderLastHost))); - log = true; - } - if (log) { - logger.info(msgBuf.toString()); - } - } - - private void resetVmNicsDeviceId(Long vmId) { - final List nics = _nicsDao.listByVmId(vmId); - Collections.sort(nics, new Comparator() { - @Override - public int compare(NicVO nic1, NicVO nic2) { - Long nicDevId1 = Long.valueOf(nic1.getDeviceId()); - Long nicDevId2 = Long.valueOf(nic2.getDeviceId()); - return nicDevId1.compareTo(nicDevId2); - } - }); - int deviceId = 0; - for (final NicVO nic : nics) { - if (nic.getDeviceId() != deviceId) { - nic.setDeviceId(deviceId); - _nicsDao.update(nic.getId(),nic); - } - deviceId ++; - } - } - - private void addExtraConfig(VirtualMachineTO vmTO) { - Map details = vmTO.getDetails(); - for (String key : details.keySet()) { - if (key.startsWith(ApiConstants.EXTRA_CONFIG)) { - vmTO.addExtraConfig(key, details.get(key)); - } - } - } - - private void handlePath(final DiskTO[] disks, final HypervisorType hypervisorType) { - if (hypervisorType != HypervisorType.KVM) { - return; - } - - if (disks != null) { - for (final DiskTO disk : disks) { - final Map details = disk.getDetails(); - final boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED)); - - if (isManaged && disk.getPath() == null) { - final Long volumeId = disk.getData().getId(); - final VolumeVO volume = _volsDao.findById(volumeId); - - disk.setPath(volume.get_iScsiName()); - - if (disk.getData() instanceof VolumeObjectTO) { - final VolumeObjectTO volTo = (VolumeObjectTO)disk.getData(); - - volTo.setPath(volume.get_iScsiName()); - } - - volume.setPath(volume.get_iScsiName()); - - _volsDao.update(volumeId, volume); - } - } - } - } - - private void handlePath(final DiskTO[] disks, final Map> iqnToData) { - if (disks != null && iqnToData != null) { - for (final DiskTO disk : disks) { - final Map details = disk.getDetails(); - final boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED)); - - if (isManaged) { - final Long volumeId = disk.getData().getId(); - final VolumeVO volume = _volsDao.findById(volumeId); - final String iScsiName = volume.get_iScsiName(); - - boolean update = false; - - final Map data = iqnToData.get(iScsiName); - - if (data != null) { - final String path = data.get(StartAnswer.PATH); - - if (path != null) { - volume.setPath(path); - - update = true; - } - - final String imageFormat = data.get(StartAnswer.IMAGE_FORMAT); - - if (imageFormat != null) { - volume.setFormat(ImageFormat.valueOf(imageFormat)); - - update = true; - } - - if (update) { - _volsDao.update(volumeId, volume); - } - } - } - } - } - } - - private void syncDiskChainChange(final StartAnswer answer) { - final VirtualMachineTO vmSpec = answer.getVirtualMachine(); - - for (final DiskTO disk : vmSpec.getDisks()) { - if (disk.getType() != Volume.Type.ISO) { - final VolumeObjectTO vol = (VolumeObjectTO)disk.getData(); - final VolumeVO volume = _volsDao.findById(vol.getId()); - if (vmSpec.getDeployAsIsInfo() != null && org.apache.commons.lang3.StringUtils.isNotBlank(vol.getPath())) { - volume.setPath(vol.getPath()); - _volsDao.update(volume.getId(), volume); - } - - if(vol.getPath() != null) { - volumeMgr.updateVolumeDiskChain(vol.getId(), vol.getPath(), vol.getChainInfo(), vol.getUpdatedDataStoreUUID()); - } else { - volumeMgr.updateVolumeDiskChain(vol.getId(), volume.getPath(), vol.getChainInfo(), vol.getUpdatedDataStoreUUID()); - } - } - } - } - - @Override - public void stop(final String vmUuid) throws ResourceUnavailableException { - try { - advanceStop(vmUuid, false); - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException(String.format("Unable to stop vm [%s] because the operation to stop timed out", vmUuid), e.getAgentId(), e); - } catch (final ConcurrentOperationException e) { - throw new CloudRuntimeException(String.format("Unable to stop vm because of a concurrent operation", vmUuid), e); + @Override + public void stop(final String vmUuid) throws ResourceUnavailableException { + try { + advanceStop(vmUuid, false); + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException(String.format("Unable to stop vm [%s] because the operation to stop timed out", vmUuid), e.getAgentId(), e); + } catch (final ConcurrentOperationException e) { + throw new CloudRuntimeException(String.format("Unable to stop vm because of a concurrent operation", vmUuid), e); } } @@ -2166,9 +1416,9 @@ void unmanageVMVolumes(VMInstanceVO vm) { } volumeMgr.unmanageVolumes(vm.getId()); - List> targets = getTargets(hostId, vm.getId()); + List> targets = vmIscsiTargetManager.getTargets(hostId, vm.getId()); if (hostId != null && CollectionUtils.isNotEmpty(targets)) { - removeDynamicTargets(hostId, targets); + vmIscsiTargetManager.removeDynamicTargets(hostId, targets); } } @@ -2187,2735 +1437,197 @@ void unmanageVMNics(VirtualMachineProfile profile, VMInstanceVO vm) { _networkMgr.unmanageNics(profile); } - private List> getVolumesToDisconnect(VirtualMachine vm) { - List> volumesToDisconnect = new ArrayList<>(); - - List volumes = _volsDao.findByInstance(vm.getId()); - - if (CollectionUtils.isEmpty(volumes)) { - return volumesToDisconnect; - } - - for (VolumeVO volume : volumes) { - StoragePoolVO storagePool = _storagePoolDao.findById(volume.getPoolId()); + @Override + public boolean sendStop(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final boolean force, final boolean checkBeforeCleanup) { + return vmStopOrchestrationService.sendStop(guru, profile, force, checkBeforeCleanup); + } - if (storagePool != null && storagePool.isManaged()) { - Map info = new HashMap<>(); + protected boolean cleanup(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final ItWorkVO work, final Event event, final boolean cleanUpEvenIfUnableToStop) { + return vmStopOrchestrationService.cleanup(guru, profile, work, event, cleanUpEvenIfUnableToStop); + } - info.put(DiskTO.STORAGE_HOST, storagePool.getHostAddress()); - info.put(DiskTO.STORAGE_PORT, String.valueOf(storagePool.getPort())); - info.put(DiskTO.IQN, volume.get_iScsiName()); - info.put(DiskTO.PROTOCOL_TYPE, (volume.getPoolType() != null) ? volume.getPoolType().toString() : null); + @Override + public void releaseVmResources(final VirtualMachineProfile profile, final boolean forced) { + vmStopOrchestrationService.releaseVmResources(profile, forced); + } - volumesToDisconnect.add(info); - } - } + @Override + public void advanceStop(final String vmUuid, final boolean cleanUpEvenIfUnableToStop) + throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + vmStopOrchestrationService.advanceStop(vmUuid, cleanUpEvenIfUnableToStop); + } - return volumesToDisconnect; + private void orchestrateStop(final String vmUuid, final boolean cleanUpEvenIfUnableToStop) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + vmStopOrchestrationService.orchestrateStop(vmUuid, cleanUpEvenIfUnableToStop); } - protected boolean sendStop(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final boolean force, final boolean checkBeforeCleanup) { - final VirtualMachine vm = profile.getVirtualMachine(); - Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - StopCommand stpCmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup); - updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stpCmd); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - stpCmd.setVlanToPersistenceMap(vlanToPersistenceMap); - } - stpCmd.setControlIp(getControlNicIpForVM(vm)); - stpCmd.setVolumesToDisconnect(getVolumesToDisconnect(vm)); - final StopCommand stop = stpCmd; - try { - Answer answer = null; - if(vm.getHostId() != null) { - answer = _agentMgr.send(vm.getHostId(), stop); - } - if (answer != null && answer instanceof StopAnswer) { - final StopAnswer stopAns = (StopAnswer)answer; - if (vm.getType() == VirtualMachine.Type.User) { - final String platform = stopAns.getPlatform(); - if (platform != null) { - final UserVmVO userVm = _userVmDao.findById(vm.getId()); - _userVmDao.loadDetails(userVm); - userVm.setDetail(VmDetailConstants.PLATFORM, platform); - _userVmDao.saveDetails(userVm); - } - } + private void setStateMachine() { + _stateMachine = VirtualMachine.State.getStateMachine(); + } - final GPUDeviceTO gpuDevice = stop.getGpuDevice(); - _resourceMgr.updateGPUDetailsForVmStop(vm, gpuDevice); - if (!answer.getResult()) { - final String details = answer.getDetails(); - logger.debug("Unable to stop VM due to {}", details); - return false; - } + protected boolean stateTransitTo(final VMInstanceVO vm, final VirtualMachine.Event e, final Long hostId, final String reservationId) throws NoTransitionException { + vm.setReservationId(reservationId); + return _stateMachine.transitTo(vm, e, new Pair<>(vm.getHostId(), hostId), _vmDao); + } - guru.finalizeStop(profile, answer); + @Override + public boolean stateTransitTo(final VirtualMachine vm1, final VirtualMachine.Event e, final Long hostId) throws NoTransitionException { + final VMInstanceVO vm = (VMInstanceVO)vm1; - final UserVmVO userVm = _userVmDao.findById(vm.getId()); - if (vm.getType() == VirtualMachine.Type.User) { - if (userVm != null) { - userVm.setPowerState(PowerState.PowerOff); - _userVmDao.update(userVm.getId(), userVm); - } - } - } else { - logger.error("Invalid answer received in response to a StopCommand for {}", vm.getInstanceName()); - return false; + final State oldState = vm.getState(); + if (oldState == State.Starting) { + if (e == Event.OperationSucceeded) { + vm.setLastHostId(hostId); } - - } catch (final AgentUnavailableException | OperationTimedoutException e) { - logger.warn("Unable to stop {} due to [{}].", vm.toString(), e.getMessage(), e); - if (!force) { - return false; + } else if (oldState == State.Stopping) { + if (e == Event.OperationSucceeded) { + vm.setLastHostId(vm.getHostId()); } } - return true; + if (e.equals(VirtualMachine.Event.DestroyRequested) || e.equals(VirtualMachine.Event.ExpungeOperation)) { + _reservationDao.setResourceId(Resource.ResourceType.user_vm, null); + _reservationDao.setResourceId(Resource.ResourceType.cpu, null); + _reservationDao.setResourceId(Resource.ResourceType.memory, null); + _reservationDao.setResourceId(Resource.ResourceType.gpu, null); + } + return _stateMachine.transitTo(vm, e, new Pair<>(vm.getHostId(), hostId), _vmDao); } - protected boolean cleanup(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final ItWorkVO work, final Event event, final boolean cleanUpEvenIfUnableToStop) { - final VirtualMachine vm = profile.getVirtualMachine(); - final State state = vm.getState(); - logger.debug("Cleaning up resources for the vm {} in {} state", vm, state); - try { - if (state == State.Starting) { - if (work != null) { - final Step step = work.getStep(); - if (step == Step.Starting && !cleanUpEvenIfUnableToStop) { - logger.warn("Unable to cleanup vm {}; work state is incorrect: {}", vm, step); - return false; - } + @Override + public void destroy(final String vmUuid, final boolean expunge) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + vmDestroyOrchestrationService.destroy(vmUuid, expunge); + } - if (step == Step.Started || step == Step.Starting || step == Step.Release) { - if (vm.getHostId() != null) { - if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { - logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Starting); - return false; - } - } - } + protected boolean checkVmOnHost(final VirtualMachine vm, final long hostId) throws AgentUnavailableException, OperationTimedoutException { + return vmDestroyOrchestrationService.checkVmOnHost(vm, hostId); + } - if (step != Step.Release && step != Step.Prepare && step != Step.Started && step != Step.Starting) { - logger.debug("Cleanup is not needed for vm {}; work state is incorrect: {}", vm, step); - return true; - } - } else { - if (vm.getHostId() != null) { - if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { - logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Starting); - return false; - } - } - } + @Override + public void storageMigration(final String vmUuid, final Map volumeToPool) { + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - } else if (state == State.Stopping) { - if (vm.getHostId() != null) { - if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { - logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Stopping); - return false; - } - } - } else if (state == State.Migrating) { - if (vm.getHostId() != null || vm.getLastHostId() != null) { - if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { - logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Migrating); - return false; - } - } - } else if (state == State.Running) { - if (!sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { - logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Running); - return false; - } + final VirtualMachine vm = _vmDao.findByUuid(vmUuid); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + orchestrateStorageMigration(vmUuid, volumeToPool); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } - } finally { - releaseVmResources(profile, cleanUpEvenIfUnableToStop); - } - - return true; - } + } else { + final Outcome outcome = vmWorkJobQueueService.migrateVmStorageThroughJobQueue(vmUuid, volumeToPool); - protected void releaseVmResources(final VirtualMachineProfile profile, final boolean forced) { - final VirtualMachine vm = profile.getVirtualMachine(); - final State state = vm.getState(); - try { - _networkMgr.release(profile, forced); - logger.debug("Successfully released network resources for the VM {} in {} state", vm, state); - } catch (final Exception e) { - logger.warn("Unable to release some network resources for the VM {} in {} state", vm, state, e); - } + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmStorage"); - try { - if (vm.getHypervisorType() != HypervisorType.BareMetal && vm.getHypervisorType() != HypervisorType.External) { - volumeMgr.release(profile); - logger.debug("Successfully released storage resources for the VM {} in {} state", vm, state); + try { + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + throw new RuntimeException("Unexpected exception", ex); } - } catch (final Exception e) { - logger.warn("Unable to release storage resources for the VM {} in {} state", vm, state, e); } + } + + private void orchestrateStorageMigration(final String vmUuid, final Map volumeToPool) { + vmOfflineStorageMigrationService.orchestrateStorageMigration(vmUuid, volumeToPool); + } + + private Answer[] attemptHypervisorMigration(VMInstanceVO vm, Map volumeToPool, Long hostId) { + return vmOfflineStorageMigrationServiceImpl.attemptHypervisorMigration(vm, volumeToPool, hostId); + } - logger.debug("Successfully cleaned up resources for the VM {} in {} state", vm, state); + private void markVolumesInPool(VMInstanceVO vm, Answer[] hypervisorMigrationResults) { + vmOfflineStorageMigrationServiceImpl.markVolumesInPool(vm, hypervisorMigrationResults); } @Override - public void advanceStop(final String vmUuid, final boolean cleanUpEvenIfUnableToStop) - throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + public void migrate(final String vmUuid, final long srcHostId, final DeployDestination dest) + throws ResourceUnavailableException, ConcurrentOperationException { final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - - VmWorkJobVO placeHolder = null; final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - placeHolder = createPlaceHolderWork(vm.getId()); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); try { - orchestrateStop(vmUuid, cleanUpEvenIfUnableToStop); + orchestrateMigrate(vmUuid, srcHostId, dest); } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } - } else { - final Outcome outcome = stopVmThroughJobQueue(vmUuid, cleanUpEvenIfUnableToStop); + final Outcome outcome = vmWorkJobQueueService.migrateVmThroughJobQueue(vmUuid, srcHostId, dest); - retrieveVmFromJobOutcome(outcome, vmUuid, "stopVm"); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVm"); try { - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (InsufficientCapacityException ex) { throw new RuntimeException("Unexpected exception", ex); } } } - private void orchestrateStop(final String vmUuid, final boolean cleanUpEvenIfUnableToStop) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + private void orchestrateMigrate(final String vmUuid, final long srcHostId, final DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException { final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - - advanceStop(vm, cleanUpEvenIfUnableToStop); - } - - private void updatePersistenceMap(Map vlanToPersistenceMap, NetworkVO networkVO) { - if (networkVO == null) { - return; - } - NetworkOfferingVO offeringVO = networkOfferingDao.findById(networkVO.getNetworkOfferingId()); - if (offeringVO == null) { - return; - } - Pair data = getVMNetworkDetails(networkVO, offeringVO.isPersistent()); - Boolean shouldDeleteNwResource = (MapUtils.isNotEmpty(vlanToPersistenceMap) && data != null) ? vlanToPersistenceMap.get(data.first()) : null; - if (data != null && (shouldDeleteNwResource == null || shouldDeleteNwResource)) { - vlanToPersistenceMap.put(data.first(), data.second()); + if (vm == null) { + logger.debug("Unable to find the Instance {}", vmUuid); + throw new CloudRuntimeException("Unable to find a Instance with ID: " + vmUuid); } + migrate(vm, srcHostId, dest); } - private Map getVlanToPersistenceMapForVM(long vmId) { - List userVmJoinVOs = userVmJoinDao.searchByIds(vmId); - Map vlanToPersistenceMap = new HashMap<>(); - if (CollectionUtils.isNotEmpty(userVmJoinVOs)) { - for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) { - NetworkVO networkVO = _networkDao.findById(userVmJoinVO.getNetworkId()); - updatePersistenceMap(vlanToPersistenceMap, networkVO); - } - } else { - VMInstanceVO vmInstanceVO = _vmDao.findById(vmId); - if (vmInstanceVO != null && vmInstanceVO.getType() == VirtualMachine.Type.DomainRouter) { - DomainRouterJoinVO routerVO = domainRouterJoinDao.findById(vmId); - NetworkVO networkVO = _networkDao.findById(routerVO.getNetworkId()); - updatePersistenceMap(vlanToPersistenceMap, networkVO); - } + protected void migrate(final VMInstanceVO vm, final long srcHostId, final DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException { + logger.info("Start preparing migration of the VM: {} to {}", vm, dest); + final long dstHostId = dest.getHost().getId(); + final Host fromHost = _hostDao.findById(srcHostId); + if (fromHost == null) { + logger.info("Unable to find the host to migrate from: {}", srcHostId); + throw new CloudRuntimeException("Unable to find the host to migrate from: " + srcHostId); } - return vlanToPersistenceMap; - } - /** - * - * @param networkVO - the network object used to determine the vlanId from the broadcast URI - * @param isPersistent - indicates if the corresponding network's network offering is Persistent - * - * @return - basically returns the vlan ID which is used to determine the - * bridge name for KVM hypervisor and based on the network and isolation type and persistent setting of the offering - * we decide whether the bridge is to be deleted (KVM) if the last VM in that host is destroyed / migrated - */ - private Pair getVMNetworkDetails(NetworkVO networkVO, boolean isPersistent) { - URI broadcastUri = networkVO.getBroadcastUri(); - if (broadcastUri != null) { - String scheme = broadcastUri.getScheme(); - String vlanId = Networks.BroadcastDomainType.getValue(broadcastUri); - boolean shouldDelete = !((networkVO.getGuestType() == Network.GuestType.L2 || networkVO.getGuestType() == Network.GuestType.Isolated) && - (scheme != null && scheme.equalsIgnoreCase("vlan")) - && isPersistent); - if (shouldDelete) { - int persistentNetworksCount = _networkDao.getOtherPersistentNetworksCount(networkVO.getId(), networkVO.getBroadcastUri().toString(), true); - if (persistentNetworksCount > 0) { - shouldDelete = false; + if (fromHost.getClusterId() != dest.getCluster().getId() && vm.getHypervisorType() != HypervisorType.VMware) { + final List volumes = _volsDao.findCreatedByInstance(vm.getId()); + for (final VolumeVO volume : volumes) { + if (!_storagePoolDao.findById(volume.getPoolId()).getScope().equals(ScopeType.ZONE)) { + logger.info("Source and destination host are not in same cluster and all volumes are not on zone wide primary store, unable to migrate to host: {}", + dest.getHost()); + throw new CloudRuntimeException(String.format( + "Source and destination host are not in same cluster and all volumes are not on zone wide primary store, unable to migrate to host: %s", + dest.getHost())); } } - return new Pair<>(vlanId, shouldDelete); } - return null; - } - private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnableToStop) throws AgentUnavailableException, OperationTimedoutException, - ConcurrentOperationException { - final State state = vm.getState(); - if (state == State.Stopped) { - logger.debug("VM is already stopped: {}", vm); - return; - } + final VirtualMachineGuru vmGuru = getVmGuru(vm); - if (state == State.Destroyed || state == State.Expunging || state == State.Error) { - logger.debug("Stopped called on {} but the state is {}", vm, state); - return; + if (vm.getState() != State.Running) { + logger.debug("VM is not Running, unable to migrate the vm {}", vm); + throw new CloudRuntimeException("VM is not Running, unable to migrate the vm currently " + vm + " , current state: " + vm.getState().toString()); } - final ItWorkVO work = _workDao.findByOutstandingWork(vm.getId(), vm.getState()); - if (work != null) { - logger.debug("Found an outstanding work item for this vm {} with state: {}, work id: {}", vm, vm.getState(), work.getId()); + AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM_MIGRATE; + if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { + alertType = AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; + } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { + alertType = AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; } - final Long hostId = vm.getHostId(); - if (hostId == null) { - if (!cleanUpEvenIfUnableToStop) { - logger.debug("HostId is null but this is not a forced stop, cannot stop vm {} with state: {}", vm, vm.getState()); - throw new CloudRuntimeException("Unable to stop " + vm); - } - try { - stateTransitTo(vm, Event.AgentReportStopped, null, null); - } catch (final NoTransitionException e) { - logger.warn(e.getMessage()); - } - if (work != null) { - logger.debug("Updating work item to Done, id: {}", work.getId()); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } - return; - } else { - HostVO host = _hostDao.findById(hostId); - if (!cleanUpEvenIfUnableToStop && vm.getState() == State.Running && host.getResourceState() == ResourceState.PrepareForMaintenance) { - logger.debug("Host is in PrepareForMaintenance state - Stop VM operation on the VM: {} is not allowed", vm); - throw new CloudRuntimeException(String.format("Stop VM operation on the VM %s is not allowed as host is preparing for maintenance mode", vm)); - } + final VirtualMachineProfile vmSrc = new VirtualMachineProfileImpl(vm); + vmSrc.setHost(fromHost); + for (final NicProfile nic : _networkMgr.getNicProfiles(vm)) { + vmSrc.addNic(nic); } - final VirtualMachineGuru vmGuru = getVmGuru(vm); - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - - try { - if (!stateTransitTo(vm, Event.StopRequested, vm.getHostId())) { - throw new ConcurrentOperationException(String.format("%s is being operated on.", vm.toString())); - } - } catch (final NoTransitionException e1) { - if (!cleanUpEvenIfUnableToStop) { - throw new CloudRuntimeException("We cannot stop " + vm + " when it is in state " + vm.getState()); - } - final boolean doCleanup = true; - logger.warn("Unable to transition the state but we're moving on because it's forced stop", e1); - - if (doCleanup) { - if (cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.StopRequested, cleanUpEvenIfUnableToStop)) { - try { - if (work != null) { - logger.debug("Updating work item to Done, id: {}", work.getId()); - } - if (!changeState(vm, Event.AgentReportStopped, null, work, Step.Done)) { - throw new CloudRuntimeException("Unable to stop " + vm); - } - - } catch (final NoTransitionException e) { - logger.warn("Unable to cleanup {}", vm); - throw new CloudRuntimeException("Unable to stop " + vm, e); - } - } else { - logger.debug("Failed to cleanup VM: {}", vm); - throw new CloudRuntimeException("Failed to cleanup " + vm + " , current state " + vm.getState()); - } - } - } - - if (vm.getState() != State.Stopping) { - throw new CloudRuntimeException("We cannot proceed with stop VM " + vm + " since it is not in 'Stopping' state, current state: " + vm.getState()); - } - - vmGuru.prepareStop(profile); - - Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - final StopCommand stop = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), false, cleanUpEvenIfUnableToStop); - stop.setControlIp(getControlNicIpForVM(vm)); - updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stop); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - stop.setVlanToPersistenceMap(vlanToPersistenceMap); - } - - boolean stopped = false; - Answer answer = null; - try { - answer = _agentMgr.send(vm.getHostId(), stop); - if (answer != null) { - if (answer instanceof StopAnswer) { - final StopAnswer stopAns = (StopAnswer)answer; - if (vm.getType() == VirtualMachine.Type.User) { - final String platform = stopAns.getPlatform(); - if (platform != null) { - final UserVmVO userVm = _userVmDao.findById(vm.getId()); - _userVmDao.loadDetails(userVm); - userVm.setDetail(VmDetailConstants.PLATFORM, platform); - _userVmDao.saveDetails(userVm); - } - } - } - stopped = answer.getResult(); - if (!stopped) { - throw new CloudRuntimeException("Unable to stop the Instance due to " + answer.getDetails()); - } - vmGuru.finalizeStop(profile, answer); - final GPUDeviceTO gpuDevice = stop.getGpuDevice(); - _resourceMgr.updateGPUDetailsForVmStop(vm, gpuDevice); - } else { - throw new CloudRuntimeException("Invalid answer received in response to a StopCommand on " + vm.instanceName); - } - - } catch (AgentUnavailableException | OperationTimedoutException e) { - logger.warn("Unable to stop {} due to [{}].", profile.toString(), e.toString(), e); - } finally { - if (!stopped) { - if (!cleanUpEvenIfUnableToStop) { - logger.warn("Unable to stop vm {}", vm); - try { - stateTransitTo(vm, Event.OperationFailed, vm.getHostId()); - } catch (final NoTransitionException e) { - logger.warn("Unable to transition the state " + vm, e); - } - throw new CloudRuntimeException("Unable to stop " + vm); - } else { - logger.warn("Unable to actually stop {} but continue with release because it's a force stop", vm); - vmGuru.finalizeStop(profile, answer); - if (HypervisorType.External.equals(profile.getHypervisorType())) { - try { - stateTransitTo(vm, VirtualMachine.Event.OperationSucceeded, null); - } catch (final NoTransitionException e) { - logger.warn("Unable to transition the state " + vm, e); - } - } - - } - } else { - if (VirtualMachine.systemVMs.contains(vm.getType())) { - HostVO systemVmHost = ApiDBUtils.findHostByTypeNameAndZoneId(vm.getDataCenterId(), vm.getHostName(), - VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType()) ? Host.Type.SecondaryStorageVM : Host.Type.ConsoleProxy); - if (systemVmHost != null) { - _agentMgr.agentStatusTransitTo(systemVmHost, Status.Event.ShutdownRequested, _nodeId); - } - } - } - } - - logger.debug("{} is stopped on the host. Proceeding to release resource held.", vm); - - releaseVmResources(profile, cleanUpEvenIfUnableToStop); - - try { - if (work != null) { - logger.debug("Updating the outstanding work item to Done, id: {}", work.getId()); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } - - boolean result = Transaction.execute(new TransactionCallbackWithException() { - @Override - public Boolean doInTransaction(TransactionStatus status) throws NoTransitionException { - boolean result = stateTransitTo(vm, Event.OperationSucceeded, null); - - if (result && VirtualMachine.Type.User.equals(vm.type) && ResourceCountRunningVMsonly.value()) { - ServiceOfferingVO offering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); - VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); - _resourceLimitMgr.decrementVmResourceCount(vm.getAccountId(), vm.isDisplay(), offering, template); - } - return result; - } - }); - - if (!result) { - throw new CloudRuntimeException("unable to stop " + vm); - } - } catch (final NoTransitionException e) { - String message = String.format("Unable to stop %s due to [%s].", vm.toString(), e.getMessage()); - logger.warn(message, e); - throw new CloudRuntimeException(message, e); - } - } - - private void setStateMachine() { - _stateMachine = VirtualMachine.State.getStateMachine(); - } - - protected boolean stateTransitTo(final VMInstanceVO vm, final VirtualMachine.Event e, final Long hostId, final String reservationId) throws NoTransitionException { - vm.setReservationId(reservationId); - return _stateMachine.transitTo(vm, e, new Pair<>(vm.getHostId(), hostId), _vmDao); - } - - @Override - public boolean stateTransitTo(final VirtualMachine vm1, final VirtualMachine.Event e, final Long hostId) throws NoTransitionException { - final VMInstanceVO vm = (VMInstanceVO)vm1; - - final State oldState = vm.getState(); - if (oldState == State.Starting) { - if (e == Event.OperationSucceeded) { - vm.setLastHostId(hostId); - } - } else if (oldState == State.Stopping) { - if (e == Event.OperationSucceeded) { - vm.setLastHostId(vm.getHostId()); - } - } - - if (e.equals(VirtualMachine.Event.DestroyRequested) || e.equals(VirtualMachine.Event.ExpungeOperation)) { - _reservationDao.setResourceId(Resource.ResourceType.user_vm, null); - _reservationDao.setResourceId(Resource.ResourceType.cpu, null); - _reservationDao.setResourceId(Resource.ResourceType.memory, null); - _reservationDao.setResourceId(Resource.ResourceType.gpu, null); - } - return _stateMachine.transitTo(vm, e, new Pair<>(vm.getHostId(), hostId), _vmDao); - } - - @Override - public void destroy(final String vmUuid, final boolean expunge) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - if (vm == null || vm.getState() == State.Destroyed || vm.getState() == State.Expunging || vm.getRemoved() != null) { - logger.debug("Unable to find vm or vm is destroyed: {}", vm); - return; - } - - logger.debug("Destroying vm {}, expunge flag {}", vm, (expunge ? "on" : "off")); - - advanceStop(vmUuid, VmDestroyForcestop.value()); - - deleteVMSnapshots(vm, expunge); - - gpuService.deallocateAllGpuDevicesForVm(vm.getId()); - - Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { - @Override - public void doInTransactionWithoutResult(final TransactionStatus status) throws CloudRuntimeException { - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - try { - if (!stateTransitTo(vm, VirtualMachine.Event.DestroyRequested, vm.getHostId())) { - logger.debug("Unable to destroy the vm because it is not in the correct state: {}", vm); - throw new CloudRuntimeException("Unable to destroy " + vm); - } else { - if (expunge) { - backupManager.checkAndRemoveBackupOfferingBeforeExpunge(vm); - if (!stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, vm.getHostId())) { - logger.debug("Unable to expunge the vm because it is not in the correct state: {}", vm); - throw new CloudRuntimeException("Unable to expunge " + vm); - } - } - } - } catch (final NoTransitionException e) { - String message = String.format("Unable to destroy %s due to [%s].", vm.toString(), e.getMessage()); - logger.debug(message, e); - throw new CloudRuntimeException(message, e); - } - } - }); - } - - /** - * Delete vm snapshots depending on vm's hypervisor type. For Vmware, vm snapshots removal is delegated to vm cleanup thread - * to reduce tasks sent to hypervisor (one tasks to delete vm snapshots and vm itself - * instead of one task for each vm snapshot plus another for the vm) - * @param vm vm - * @param expunge indicates if vm should be expunged - */ - private void deleteVMSnapshots(VMInstanceVO vm, boolean expunge) { - if (! vm.getHypervisorType().equals(HypervisorType.VMware)) { - if (!_vmSnapshotMgr.deleteAllVMSnapshots(vm.getId(), null)) { - logger.debug("Unable to delete all Snapshots for {}", vm); - throw new CloudRuntimeException("Unable to delete Instance Snapshots for " + vm); - } - } - else { - if (expunge) { - _vmSnapshotMgr.deleteVMSnapshotsFromDB(vm.getId(), false); - } - } - } - - protected boolean checkVmOnHost(final VirtualMachine vm, final long hostId) throws AgentUnavailableException, OperationTimedoutException { - final Answer answer = _agentMgr.send(hostId, new CheckVirtualMachineCommand(vm.getInstanceName())); - if (answer == null || !answer.getResult()) { - return false; - } - if (answer instanceof CheckVirtualMachineAnswer) { - final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer)answer; - if (vmAnswer.getState() == PowerState.PowerOff) { - return false; - } - } - - UserVmVO userVm = _userVmDao.findById(vm.getId()); - if (userVm != null) { - List vmSnapshots = _vmSnapshotDao.findByVm(vm.getId()); - RestoreVMSnapshotCommand command = _vmSnapshotMgr.createRestoreCommand(userVm, vmSnapshots); - if (command != null) { - RestoreVMSnapshotAnswer restoreVMSnapshotAnswer = (RestoreVMSnapshotAnswer) _agentMgr.send(hostId, command); - if (restoreVMSnapshotAnswer == null || !restoreVMSnapshotAnswer.getResult()) { - logger.warn("Unable to restore the Instance Snapshot from image file after live migration of Instance with vmsnapshots: {}", restoreVMSnapshotAnswer == null ? "null answer" : restoreVMSnapshotAnswer.getDetails()); - } - } - } - - return true; - } - - @Override - public void storageMigration(final String vmUuid, final Map volumeToPool) { - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - - final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - orchestrateStorageMigration(vmUuid, volumeToPool); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - } else { - final Outcome outcome = migrateVmStorageThroughJobQueue(vmUuid, volumeToPool); - - retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmStorage"); - - try { - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (ResourceUnavailableException | InsufficientCapacityException ex) { - throw new RuntimeException("Unexpected exception", ex); - } - } - } - - private void orchestrateStorageMigration(final String vmUuid, final Map volumeToPool) { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - - Map volumeToPoolMap = prepareVmStorageMigration(vm, volumeToPool); - - try { - logger.debug("Offline migration of {} vm {} with volumes", - vm.getHypervisorType().toString(), - vm.getInstanceName()); - - migrateThroughHypervisorOrStorage(vm, volumeToPoolMap); - - } catch (ConcurrentOperationException - | InsufficientCapacityException - | StorageUnavailableException e) { - String msg = String.format("Failed to migrate VM: %s", vmUuid); - logger.warn(msg, e); - throw new CloudRuntimeException(msg, e); - } finally { - try { - stateTransitTo(vm, Event.AgentReportStopped, null); - } catch (final NoTransitionException e) { - String anotherMEssage = String.format("failed to change vm state of VM: %s", vmUuid); - logger.warn(anotherMEssage, e); - throw new CloudRuntimeException(anotherMEssage, e); - } - } - } - - private Answer[] attemptHypervisorMigration(VMInstanceVO vm, Map volumeToPool, Long hostId) { - if (hostId == null) { - return null; - } - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType()); - - List commandsToSend = hvGuru.finalizeMigrate(vm, volumeToPool); - - if (CollectionUtils.isNotEmpty(commandsToSend)) { - Commands commandsContainer = new Commands(Command.OnError.Stop); - commandsContainer.addCommands(commandsToSend); - - try { - return _agentMgr.send(hostId, commandsContainer); - } catch (AgentUnavailableException | OperationTimedoutException e) { - logger.warn("Hypervisor migration failed for the VM: {}", vm, e); - } - } - return null; - } - - private void afterHypervisorMigrationCleanup(VMInstanceVO vm, Map volumeToPool, Long sourceClusterId, Answer[] hypervisorMigrationResults) throws InsufficientCapacityException { - logger.debug("Cleaning up after hypervisor pool migration volumes for VM {}({})", vm.getInstanceName(), vm.getUuid()); - - StoragePool rootVolumePool = null; - if (MapUtils.isNotEmpty(volumeToPool)) { - for (Map.Entry entry : volumeToPool.entrySet()) { - if (Type.ROOT.equals(entry.getKey().getVolumeType())) { - rootVolumePool = entry.getValue(); - break; - } - } - } - setDestinationPoolAndReallocateNetwork(rootVolumePool, vm); - Long destClusterId = rootVolumePool != null ? rootVolumePool.getClusterId() : null; - if (destClusterId != null && !destClusterId.equals(sourceClusterId)) { - logger.debug("Resetting lastHost for VM {}({})", vm.getInstanceName(), vm.getUuid()); - vm.setLastHostId(null); - vm.setPodIdToDeployIn(rootVolumePool.getPodId()); - } - - markVolumesInPool(vm, hypervisorMigrationResults); - } - - private void markVolumesInPool(VMInstanceVO vm, Answer[] hypervisorMigrationResults) { - MigrateVmToPoolAnswer relevantAnswer = null; - if (hypervisorMigrationResults.length == 1 && !hypervisorMigrationResults[0].getResult()) { - throw new CloudRuntimeException(String.format("VM ID: %s migration failed. %s", vm.getUuid(), hypervisorMigrationResults[0].getDetails())); - } - for (Answer answer : hypervisorMigrationResults) { - logger.debug("Received an {}: {}", answer.getClass().getSimpleName(), answer); - if (answer instanceof MigrateVmToPoolAnswer) { - relevantAnswer = (MigrateVmToPoolAnswer) answer; - } - } - if (relevantAnswer == null) { - throw new CloudRuntimeException("No relevant migration results found"); - } - List results = relevantAnswer.getVolumeTos(); - if (results == null) { - results = new ArrayList<>(); - } - List volumes = _volsDao.findUsableVolumesForInstance(vm.getId()); - logger.debug("Found {} volumes for VM {}(uuid:{}, id:{})", results.size(), vm.getInstanceName(), vm.getUuid(), vm.getId()); - for (VolumeObjectTO result : results ) { - logger.debug("Updating volume ({}) with path '{}' on pool '{}'", result.getUuid(), result.getPath(), result.getDataStoreUuid()); - VolumeVO volume = _volsDao.findById(result.getId()); - StoragePool pool = _storagePoolDao.findPoolByUUID(result.getDataStoreUuid()); - if (volume == null || pool == null) { - continue; - } - volume.setPath(result.getPath()); - volume.setPoolId(pool.getId()); - volume.setPoolType(pool.getPoolType()); - if (result.getChainInfo() != null) { - volume.setChainInfo(result.getChainInfo()); - } - _volsDao.update(volume.getId(), volume); - } - } - - private void migrateThroughHypervisorOrStorage(VMInstanceVO vm, Map volumeToPool) throws StorageUnavailableException, InsufficientCapacityException { - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - Pair vmClusterAndHost = findClusterAndHostIdForVm(vm); - final Long sourceClusterId = vmClusterAndHost.first(); - final Long sourceHostId = vmClusterAndHost.second(); - Answer[] hypervisorMigrationResults = attemptHypervisorMigration(vm, volumeToPool, sourceHostId); - boolean migrationResult = false; - if (hypervisorMigrationResults == null) { - migrationResult = volumeMgr.storageMigration(profile, volumeToPool); - if (migrationResult) { - postStorageMigrationCleanup(vm, volumeToPool, _hostDao.findById(sourceHostId), sourceClusterId); - } else { - logger.debug("Storage migration failed"); - } - } else { - afterHypervisorMigrationCleanup(vm, volumeToPool, sourceClusterId, hypervisorMigrationResults); - } - } - - private Map prepareVmStorageMigration(VMInstanceVO vm, Map volumeToPool) { - Map volumeToPoolMap = new HashMap<>(); - if (MapUtils.isEmpty(volumeToPool)) { - throw new CloudRuntimeException(String.format("Unable to migrate %s: missing volume to pool mapping.", vm.toString())); - } - Cluster cluster = null; - Long dataCenterId = null; - for (Map.Entry entry: volumeToPool.entrySet()) { - StoragePool pool = _storagePoolDao.findById(entry.getValue()); - if (pool.getClusterId() != null) { - cluster = _clusterDao.findById(pool.getClusterId()); - break; - } - dataCenterId = pool.getDataCenterId(); - } - Long podId = null; - Long clusterId = null; - if (cluster != null) { - dataCenterId = cluster.getDataCenterId(); - podId = cluster.getPodId(); - clusterId = cluster.getId(); - } - if (dataCenterId == null) { - String msg = "Unable to migrate Instance: failed to create deployment destination with given volume to pool map"; - logger.debug(msg); - throw new CloudRuntimeException(msg); - } - final DataCenterDeployment destination = new DataCenterDeployment(dataCenterId, podId, clusterId, null, null, null); - // Create a map of which volume should go in which storage pool. - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - volumeToPoolMap = createMappingVolumeAndStoragePool(profile, destination, volumeToPool); - try { - stateTransitTo(vm, Event.StorageMigrationRequested, null); - } catch (final NoTransitionException e) { - String msg = String.format("Unable to migrate Instance: %s", vm.getUuid()); - logger.warn(msg, e); - throw new CloudRuntimeException(msg, e); - } - return volumeToPoolMap; - } - - private void checkDestinationForTags(StoragePool destPool, VMInstanceVO vm) { - List vols = _volsDao.findUsableVolumesForInstance(vm.getId()); - - List storageTags = storageMgr.getStoragePoolTagList(destPool.getId()); - for(Volume vol : vols) { - DiskOfferingVO diskOffering = _diskOfferingDao.findById(vol.getDiskOfferingId()); - List volumeTags = StringUtils.csvTagsToList(diskOffering.getTags()); - if(! matches(volumeTags, storageTags)) { - String msg = String.format("destination pool '%s' with tags '%s', does not support the volume diskoffering for volume '%s' (tags: '%s') ", - destPool.getName(), - StringUtils.listToCsvTags(storageTags), - vol.getName(), - StringUtils.listToCsvTags(volumeTags) - ); - throw new CloudRuntimeException(msg); - } - } - } - - static boolean matches(List volumeTags, List storagePoolTags) { - boolean result = true; - if (volumeTags != null) { - for (String tag : volumeTags) { - if (storagePoolTags == null || !storagePoolTags.contains(tag)) { - result = false; - break; - } - } - } - return result; - } - - private void postStorageMigrationCleanup(VMInstanceVO vm, Map volumeToPool, HostVO srcHost, Long srcClusterId) throws InsufficientCapacityException { - StoragePool rootVolumePool = null; - if (MapUtils.isNotEmpty(volumeToPool)) { - for (Map.Entry entry : volumeToPool.entrySet()) { - if (Type.ROOT.equals(entry.getKey().getVolumeType())) { - rootVolumePool = entry.getValue(); - break; - } - } - } - setDestinationPoolAndReallocateNetwork(rootVolumePool, vm); - - vm.setLastHostId(null); - if (rootVolumePool != null) { - vm.setPodIdToDeployIn(rootVolumePool.getPodId()); - } - - if (vm.getHypervisorType().equals(HypervisorType.VMware)) { - afterStorageMigrationVmwareVMCleanup(rootVolumePool, vm, srcHost, srcClusterId); - } - } - - private void setDestinationPoolAndReallocateNetwork(StoragePool destPool, VMInstanceVO vm) throws InsufficientCapacityException { - if (destPool != null && destPool.getPodId() != null && !destPool.getPodId().equals(vm.getPodIdToDeployIn())) { - logger.debug("as the pod for vm {} has changed we are reallocating its network", vm.getInstanceName()); - final DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), destPool.getPodId(), null, null, null, null); - final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, null, null, null, null); - _networkMgr.reallocate(vmProfile, plan); - } - } - - private void afterStorageMigrationVmwareVMCleanup(StoragePool destPool, VMInstanceVO vm, HostVO srcHost, Long srcClusterId) { - final Long destClusterId = destPool.getClusterId(); - if (srcClusterId != null && destClusterId != null && ! srcClusterId.equals(destClusterId) && srcHost != null) { - final String srcDcName = _clusterDetailsDao.getVmwareDcName(srcClusterId); - final String destDcName = _clusterDetailsDao.getVmwareDcName(destClusterId); - if (srcDcName != null && destDcName != null && !srcDcName.equals(destDcName)) { - removeStaleVmFromSource(vm, srcHost); - } - } - } - - private void removeStaleVmFromSource(VMInstanceVO vm, HostVO srcHost) { - logger.debug("Since VM's storage was successfully migrated across VMware Datacenters, unregistering VM: {} from source host: {}", - vm, srcHost); - final UnregisterVMCommand uvc = new UnregisterVMCommand(vm.getInstanceName()); - uvc.setCleanupVmFiles(true); - try { - _agentMgr.send(srcHost.getId(), uvc); - } catch (AgentUnavailableException | OperationTimedoutException e) { - throw new CloudRuntimeException(String.format( - "Failed to unregister VM: %s from source host: %s after successfully migrating VM's storage across VMware Datacenters", - vm, srcHost), e); - } - } - - @Override - public void migrate(final String vmUuid, final long srcHostId, final DeployDestination dest) - throws ResourceUnavailableException, ConcurrentOperationException { - - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - orchestrateMigrate(vmUuid, srcHostId, dest); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - } else { - final Outcome outcome = migrateVmThroughJobQueue(vmUuid, srcHostId, dest); - - retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVm"); - - try { - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (InsufficientCapacityException ex) { - throw new RuntimeException("Unexpected exception", ex); - } - } - } - - private void orchestrateMigrate(final String vmUuid, final long srcHostId, final DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - if (vm == null) { - logger.debug("Unable to find the Instance {}", vmUuid); - throw new CloudRuntimeException("Unable to find a Instance with ID: " + vmUuid); - } - migrate(vm, srcHostId, dest); - } - - protected void migrate(final VMInstanceVO vm, final long srcHostId, final DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException { - logger.info("Start preparing migration of the VM: {} to {}", vm, dest); - final long dstHostId = dest.getHost().getId(); - final Host fromHost = _hostDao.findById(srcHostId); - if (fromHost == null) { - logger.info("Unable to find the host to migrate from: {}", srcHostId); - throw new CloudRuntimeException("Unable to find the host to migrate from: " + srcHostId); - } - - if (fromHost.getClusterId() != dest.getCluster().getId() && vm.getHypervisorType() != HypervisorType.VMware) { - final List volumes = _volsDao.findCreatedByInstance(vm.getId()); - for (final VolumeVO volume : volumes) { - if (!_storagePoolDao.findById(volume.getPoolId()).getScope().equals(ScopeType.ZONE)) { - logger.info("Source and destination host are not in same cluster and all volumes are not on zone wide primary store, unable to migrate to host: {}", - dest.getHost()); - throw new CloudRuntimeException(String.format( - "Source and destination host are not in same cluster and all volumes are not on zone wide primary store, unable to migrate to host: %s", - dest.getHost())); - } - } - } - - final VirtualMachineGuru vmGuru = getVmGuru(vm); - - if (vm.getState() != State.Running) { - logger.debug("VM is not Running, unable to migrate the vm {}", vm); - throw new CloudRuntimeException("VM is not Running, unable to migrate the vm currently " + vm + " , current state: " + vm.getState().toString()); - } - - AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM_MIGRATE; - if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { - alertType = AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; - } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { - alertType = AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; - } - - final VirtualMachineProfile vmSrc = new VirtualMachineProfileImpl(vm); - vmSrc.setHost(fromHost); - for (final NicProfile nic : _networkMgr.getNicProfiles(vm)) { - vmSrc.addNic(nic); - } - - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()), null, null); - profile.setHost(dest.getHost()); - - _networkMgr.prepareNicForMigration(profile, dest); - volumeMgr.prepareForMigration(profile, dest); - profile.setConfigDriveLabel(VmConfigDriveLabel.value()); - updateOverCommitRatioForVmProfile(profile, dest.getHost().getClusterId()); - - final VirtualMachineTO to = toVmTO(profile); - final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to); - setVmNetworkDetails(vm, to); - - ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId()); - work.setStep(Step.Prepare); - work.setResourceType(ItWorkVO.ResourceType.Host); - work.setResourceId(dstHostId); - work = _workDao.persist(work); - - Answer pfma = null; - try { - pfma = _agentMgr.send(dstHostId, pfmc); - if (pfma == null || !pfma.getResult()) { - final String details = pfma != null ? pfma.getDetails() : "null answer returned"; - final String msg = "Unable to prepare for migration due to " + details; - logger.error("Failed to prepare destination host {} for migration of VM {} : {}", dstHostId, vm.getInstanceName(), details); - pfma = null; - throw new AgentUnavailableException(msg, dstHostId); - } - logger.debug("Successfully prepared destination host {} for migration of VM {} ", dstHostId, vm.getInstanceName()); - } catch (final OperationTimedoutException e1) { - throw new AgentUnavailableException("Operation timed out", dstHostId); - } finally { - if (pfma == null) { - _networkMgr.rollbackNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), dstHostId); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } - } - - vm.setLastHostId(srcHostId); - _vmDao.resetVmPowerStateTracking(vm.getId()); - try { - if (vm.getHostId() == null || vm.getHostId() != srcHostId || !changeState(vm, Event.MigrationRequested, dstHostId, work, Step.Migrating)) { - _networkMgr.rollbackNicForMigration(vmSrc, profile); - if (vm != null) { - volumeMgr.release(vm.getId(), dstHostId); - } - - String msg = "Migration cancelled because state has changed: " + vm; - logger.warn(msg); - throw new ConcurrentOperationException(msg); - } - } catch (final NoTransitionException e1) { - _networkMgr.rollbackNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), dstHostId); - String msg = String.format("Migration cancelled for VM %s due to state transition failure: %s", - vm.getInstanceName(), e1.getMessage()); - logger.warn(msg, e1); - throw new ConcurrentOperationException("Migration cancelled because " + e1.getMessage()); - } catch (final CloudRuntimeException e2) { - _networkMgr.rollbackNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), dstHostId); - String msg = String.format("Migration cancelled for VM %s due to runtime exception: %s", - vm.getInstanceName(), e2.getMessage()); - logger.error(msg, e2); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - try { - stateTransitTo(vm, Event.OperationFailed, srcHostId); - } catch (final NoTransitionException e3) { - logger.warn(e3.getMessage()); - } - throw new CloudRuntimeException("Migration cancelled because " + e2.getMessage()); - } - - boolean migrated = false; - Map dpdkInterfaceMapping = new HashMap<>(); - try { - final MigrateCommand mc = buildMigrateCommand(vm, to, dest, pfma, dpdkInterfaceMapping); - - try { - final Answer ma = _agentMgr.send(vm.getLastHostId(), mc); - if (ma == null || !ma.getResult()) { - final String details = ma != null ? ma.getDetails() : "null answer returned"; - String msg = String.format("Migration command failed for VM %s on source host id=%s to destination host %s: %s", - vm.getInstanceName(), vm.getLastHostId(), dstHostId, details); - logger.error(msg); - throw new CloudRuntimeException(details); - } - logger.info("Migration command successful for VM {}", vm.getInstanceName()); - } catch (final OperationTimedoutException e) { - boolean success = false; - if (HypervisorType.KVM.equals(vm.getHypervisorType())) { - try { - final Answer answer = _agentMgr.send(vm.getHostId(), new CheckVirtualMachineCommand(vm.getInstanceName())); - if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) { - final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer) answer; - if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) { - logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, vm.getHostId())); - success = true; - } - } - } catch (Exception ex) { - logger.error(String.format("Failed to get state of VM %s on destination host %s: %s", vm, vm.getHostId(), ex.getMessage())); - } - } - if (!success) { - if (e.isActive()) { - logger.warn("Active migration command so scheduling a restart for {}", vm, e); - _haMgr.scheduleRestart(vm, true); - - throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId); - } - } - } - - try { - if (!changeState(vm, VirtualMachine.Event.OperationSucceeded, dstHostId, work, Step.Started)) { - throw new ConcurrentOperationException("Unable to change the state for " + vm); - } - } catch (final NoTransitionException e1) { - throw new ConcurrentOperationException("Unable to change state due to " + e1.getMessage()); - } - - try { - if (!checkVmOnHost(vm, dstHostId)) { - logger.error("Migration verification failed for VM {} : VM not found on destination host {} ", vm.getInstanceName(), dstHostId); - try { - _agentMgr.send(srcHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null); - } catch (final AgentUnavailableException e) { - logger.error("AgentUnavailableException while cleanup on source host: {}", fromHost, e); - } - cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true); - throw new CloudRuntimeException("Unable to complete migration for " + vm); - } - } catch (final OperationTimedoutException e) { - logger.warn("Error while checking the vm {} on host {}", vm, dest.getHost(), e); - } - migrated = true; - } finally { - if (!migrated) { - logger.info("Migration was unsuccessful. Cleaning up: {}", vm); - _networkMgr.rollbackNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), dstHostId); - // deallocate GPU devices for the VM on the destination host - gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), dstHostId); - - _alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), - "Unable to migrate vm " + vm.getInstanceName() + " from host " + fromHost.getName() + " in zone " + dest.getDataCenter().getName() + " and pod " + - dest.getPod().getName(), "Migrate Command failed. Please check logs."); - try { - _agentMgr.send(dstHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null); - } catch (final AgentUnavailableException ae) { - logger.warn("Destination host {} unavailable for cleanup after failed migration of VM {}", dstHostId, vm.getInstanceName(), ae); - } - _networkMgr.setHypervisorHostname(profile, dest, false); - try { - stateTransitTo(vm, Event.OperationFailed, srcHostId); - } catch (final NoTransitionException e) { - logger.warn(e.getMessage()); - } - } else { - logger.info("Migration completed successfully for VM %s" + vm); - _networkMgr.commitNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), srcHostId); - // deallocate GPU devices for the VM on the src host after migration is complete - gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), srcHostId); - _networkMgr.setHypervisorHostname(profile, dest, true); - recreateCheckpointsKvmOnVmAfterMigration(vm, dstHostId); - - updateVmPod(vm, dstHostId); - } - - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } - } - - /** - * Create and set parameters for the {@link MigrateCommand} used in the migration and scaling of VMs. - */ - protected MigrateCommand buildMigrateCommand(VMInstanceVO vmInstance, VirtualMachineTO virtualMachineTO, DeployDestination destination, Answer answer, - Map dpdkInterfaceMapping) { - final boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vmInstance.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); - final MigrateCommand migrateCommand = new MigrateCommand(vmInstance.getInstanceName(), destination.getHost().getPrivateIpAddress(), isWindows, virtualMachineTO, - getExecuteInSequence(vmInstance.getHypervisorType())); - - Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vmInstance.getId()); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - logger.debug("Setting VLAN persistence to [{}] as part of migrate command for VM [{}].", new Gson().toJson(vlanToPersistenceMap), virtualMachineTO); - migrateCommand.setVlanToPersistenceMap(vlanToPersistenceMap); - } - - logger.debug("Setting auto convergence to: {}", StorageManager.KvmAutoConvergence.value()); - migrateCommand.setAutoConvergence(StorageManager.KvmAutoConvergence.value()); - migrateCommand.setHostGuid(destination.getHost().getGuid()); - - PrepareForMigrationAnswer prepareForMigrationAnswer = (PrepareForMigrationAnswer) answer; - - Map answerDpdkInterfaceMapping = prepareForMigrationAnswer.getDpdkInterfaceMapping(); - if (MapUtils.isNotEmpty(answerDpdkInterfaceMapping) && dpdkInterfaceMapping != null) { - logger.debug("Setting DPDK interface mapping to [{}] as part of migrate command for VM [{}].", new Gson().toJson(vlanToPersistenceMap), - virtualMachineTO); - dpdkInterfaceMapping.putAll(answerDpdkInterfaceMapping); - migrateCommand.setDpdkInterfaceMapping(dpdkInterfaceMapping); - } - - Integer newVmCpuShares = prepareForMigrationAnswer.getNewVmCpuShares(); - if (newVmCpuShares != null) { - logger.debug("Setting CPU shares to [{}] as part of migrate command for VM [{}].", newVmCpuShares, virtualMachineTO); - migrateCommand.setNewVmCpuShares(newVmCpuShares); - } - - return migrateCommand; - } - - private void updateVmPod(VMInstanceVO vm, long dstHostId) { - // update the VMs pod - HostVO host = _hostDao.findById(dstHostId); - VMInstanceVO newVm = _vmDao.findById(vm.getId()); - newVm.setPodIdToDeployIn(host.getPodId()); - _vmDao.persist(newVm); - } - - /** - * We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user. - * If the user did not enter a complete mapping, the volumes that were left behind will be auto mapped using {@link #createStoragePoolMappingsForVolumes(VirtualMachineProfile, DataCenterDeployment, Map, List)} - */ - protected Map createMappingVolumeAndStoragePool(VirtualMachineProfile profile, Host targetHost, Map userDefinedMapOfVolumesAndStoragePools) { - return createMappingVolumeAndStoragePool(profile, - new DataCenterDeployment(targetHost.getDataCenterId(), targetHost.getPodId(), targetHost.getClusterId(), targetHost.getId(), null, null), - userDefinedMapOfVolumesAndStoragePools); - } - - private Map createMappingVolumeAndStoragePool(final VirtualMachineProfile profile, final DataCenterDeployment plan, final Map userDefinedMapOfVolumesAndStoragePools) { - Host targetHost = null; - if (plan.getHostId() != null) { - targetHost = _hostDao.findById(plan.getHostId()); - } - Map volumeToPoolObjectMap = buildMapUsingUserInformation(profile, targetHost, userDefinedMapOfVolumesAndStoragePools); - - List volumesNotMapped = findVolumesThatWereNotMappedByTheUser(profile, volumeToPoolObjectMap); - createStoragePoolMappingsForVolumes(profile, plan, volumeToPoolObjectMap, volumesNotMapped); - return volumeToPoolObjectMap; - } - - /** - * Given the map of volume to target storage pool entered by the user, we check for other volumes that the VM might have and were not configured. - * This map can be then used by CloudStack to find new target storage pools according to the target host. - */ - protected List findVolumesThatWereNotMappedByTheUser(VirtualMachineProfile profile, Map volumeToStoragePoolObjectMap) { - List allVolumes = _volsDao.findUsableVolumesForInstance(profile.getId()); - List volumesNotMapped = new ArrayList<>(); - for (Volume volume : allVolumes) { - if (!volumeToStoragePoolObjectMap.containsKey(volume)) { - volumesNotMapped.add(volume); - } - } - return volumesNotMapped; - } - - /** - * Builds the map of storage pools and volumes with the information entered by the user. Before creating the an entry we validate if the migration is feasible checking if the migration is allowed and if the target host can access the defined target storage pool. - */ - protected Map buildMapUsingUserInformation(VirtualMachineProfile profile, Host targetHost, Map userDefinedVolumeToStoragePoolMap) { - Map volumeToPoolObjectMap = new HashMap<>(); - if (MapUtils.isEmpty(userDefinedVolumeToStoragePoolMap)) { - return volumeToPoolObjectMap; - } - for(Long volumeId: userDefinedVolumeToStoragePoolMap.keySet()) { - VolumeVO volume = _volsDao.findById(volumeId); - - Long poolId = userDefinedVolumeToStoragePoolMap.get(volumeId); - StoragePoolVO targetPool = _storagePoolDao.findById(poolId); - StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId()); - - executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPool, volume, targetPool); - if (targetHost != null && _poolHostDao.findByPoolHost(targetPool.getId(), targetHost.getId()) == null) { - throw new CloudRuntimeException( - String.format("Cannot migrate the volume [%s] to the storage pool [%s] while migrating VM [%s] to target host [%s]. The host does not have access to the storage pool entered.", - volume.getUuid(), targetPool.getUuid(), profile.getUuid(), targetHost.getUuid())); - } - if (currentPool.getId() == targetPool.getId()) { - logger.info("The volume [{}] is already allocated in storage pool [{}].", volume.getUuid(), targetPool.getUuid()); - } - volumeToPoolObjectMap.put(volume, targetPool); - } - return volumeToPoolObjectMap; - } - - /** - * Executes the managed storage checks for the mapping entered by the user. The checks execute by this method are the following. - *
    - *
  • If the current storage pool of the volume is not a managed storage, we do not need to validate anything here. - *
  • If the current storage pool is a managed storage and the target storage pool ID is different from the current one, we throw an exception. - *
  • If the current storage pool is a managed storage and explicitly declared its capable of migration to alternate storage pools - *
- */ - protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) { - if (!currentPool.isManaged() || currentPool.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { - return; - } - if (currentPool.getId() == targetPool.getId()) { - return; - } - - Map details = _storagePoolDao.getDetails(currentPool.getId()); - if (details != null && Boolean.parseBoolean(details.get(Storage.Capability.ALLOW_MIGRATE_OTHER_POOLS.toString()))) { - return; - } - throw new CloudRuntimeException(String.format("Currently, a volume on managed storage can only be 'migrated' to itself " + "[volumeId=%s, currentStoragePoolId=%s, targetStoragePoolId=%s].", - volume.getUuid(), currentPool.getUuid(), targetPool.getUuid())); - } - - /** - * For each one of the volumes we will map it to a storage pool that is available via the target host. - * An exception is thrown if we cannot find a storage pool that is accessible in the target host to migrate the volume to. - */ - protected void createStoragePoolMappingsForVolumes(VirtualMachineProfile profile, DataCenterDeployment plan, Map volumeToPoolObjectMap, List volumesNotMapped) { - for (Volume volume : volumesNotMapped) { - StoragePoolVO currentPool = _storagePoolDao.findById(volume.getPoolId()); - - Host targetHost = null; - if (plan.getHostId() != null) { - targetHost = _hostDao.findById(plan.getHostId()); - } - executeManagedStorageChecksWhenTargetStoragePoolNotProvided(targetHost, currentPool, volume); - if (ScopeType.HOST.equals(currentPool.getScope()) || isStorageCrossClusterMigration(plan.getClusterId(), currentPool)) { - createVolumeToStoragePoolMappingIfPossible(profile, plan, volumeToPoolObjectMap, volume, currentPool); - } else if (shouldMapVolume(profile, currentPool)){ - volumeToPoolObjectMap.put(volume, currentPool); - } - } - } - - /** - * Returns true if it should map the volume for a storage pool to migrate. - *

- * Some context: VMware migration workflow requires all volumes to be mapped (even if volume stays on its current pool); - * however, this is not necessary/desirable for the KVM flow. - */ - protected boolean shouldMapVolume(VirtualMachineProfile profile, StoragePoolVO currentPool) { - boolean isManaged = currentPool.isManaged(); - boolean isNotKvm = HypervisorType.KVM != profile.getHypervisorType(); - return isNotKvm || isManaged; - } - - /** - * Executes the managed storage checks for the volumes that the user has not entered a mapping of . The following checks are performed. - *
    - *
  • If the current storage pool is not a managed storage, we do not need to proceed with this method; - *
  • We check if the target host has access to the current managed storage pool. If it does not have an exception will be thrown. - *
- */ - protected void executeManagedStorageChecksWhenTargetStoragePoolNotProvided(Host targetHost, StoragePoolVO currentPool, Volume volume) { - if (!currentPool.isManaged()) { - return; - } - if (targetHost != null && _poolHostDao.findByPoolHost(currentPool.getId(), targetHost.getId()) == null) { - throw new CloudRuntimeException(String.format("The target host does not have access to the volume's managed storage pool. [volumeId=%s, storageId=%s, targetHostId=%s].", volume.getUuid(), - currentPool.getUuid(), targetHost.getUuid())); - } - } - - /** - * Return true if the VM migration is a cross cluster migration. To execute that, we check if the volume current storage pool cluster is different from the target cluster. - */ - protected boolean isStorageCrossClusterMigration(Long clusterId, StoragePoolVO currentPool) { - return clusterId != null && ScopeType.CLUSTER.equals(currentPool.getScope()) && !currentPool.getClusterId().equals(clusterId); - } - - /** - * We will add a mapping of volume to storage pool if needed. The conditions to add a mapping are the following: - *
    - *
  • The candidate storage pool where the volume is to be allocated can be accessed by the target host - *
  • If no storage pool is found to allocate the volume we throw an exception. - *
- * - * Side note: this method should only be called if the volume is on local storage or if we are executing a cross cluster migration. - */ - protected void createVolumeToStoragePoolMappingIfPossible(VirtualMachineProfile profile, DataCenterDeployment plan, Map volumeToPoolObjectMap, Volume volume, - StoragePoolVO currentPool) { - List storagePoolList = getCandidateStoragePoolsToMigrateLocalVolume(profile, plan, volume); - - if (CollectionUtils.isEmpty(storagePoolList)) { - String msg; - if (plan.getHostId() != null) { - Host targetHost = _hostDao.findById(plan.getHostId()); - msg = String.format("There are no storage pools available at the target host [%s] to migrate volume [%s]", targetHost.getUuid(), volume.getUuid()); - } else { - Cluster targetCluster = _clusterDao.findById(plan.getClusterId()); - msg = String.format("There are no storage pools available in the target cluster [%s] to migrate volume [%s]", targetCluster.getUuid(), volume.getUuid()); - } - throw new CloudRuntimeException(msg); - } - - Collections.shuffle(storagePoolList); - boolean candidatePoolsListContainsVolumeCurrentStoragePool = false; - for (StoragePool storagePool : storagePoolList) { - if (storagePool.getId() == currentPool.getId()) { - candidatePoolsListContainsVolumeCurrentStoragePool = true; - break; - } - - } - if (!candidatePoolsListContainsVolumeCurrentStoragePool) { - volumeToPoolObjectMap.put(volume, _storagePoolDao.findByUuid(storagePoolList.get(0).getUuid())); - } - } - - /** - * We use {@link StoragePoolAllocator} objects to find storage pools for given DataCenterDeployment where we would be able to allocate the given volume. - */ - protected List getCandidateStoragePoolsToMigrateLocalVolume(VirtualMachineProfile profile, DataCenterDeployment plan, Volume volume) { - List poolList = new ArrayList<>(); - - DiskOfferingVO diskOffering = _diskOfferingDao.findById(volume.getDiskOfferingId()); - DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType()); - ExcludeList avoid = new ExcludeList(); - - StoragePoolVO volumeStoragePool = _storagePoolDao.findById(volume.getPoolId()); - if (volumeStoragePool.isLocal()) { - diskProfile.setUseLocalStorage(true); - } - for (StoragePoolAllocator allocator : _storagePoolAllocators) { - List poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL); - if (CollectionUtils.isEmpty(poolListFromAllocator)) { - continue; - } - for (StoragePool pool : poolListFromAllocator) { - if (pool.isLocal() || isStorageCrossClusterMigration(plan.getClusterId(), volumeStoragePool)) { - poolList.add(pool); - } - } - } - return poolList; - } - - private void moveVmToMigratingState(final T vm, final Long hostId, final ItWorkVO work) throws ConcurrentOperationException { - try { - if (!changeState(vm, Event.MigrationRequested, hostId, work, Step.Migrating)) { - logger.error("Migration cancelled because state has changed: " + vm); - throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm); - } - } catch (final NoTransitionException e) { - logger.error("Migration cancelled because " + e.getMessage(), e); - throw new ConcurrentOperationException("Migration cancelled because " + e.getMessage()); - } - } - - private void moveVmOutofMigratingStateOnSuccess(final T vm, final Long hostId, final ItWorkVO work) throws ConcurrentOperationException { - try { - if (!changeState(vm, Event.OperationSucceeded, hostId, work, Step.Started)) { - logger.error("Unable to change the state for " + vm); - throw new ConcurrentOperationException("Unable to change the state for " + vm); - } - } catch (final NoTransitionException e) { - logger.error("Unable to change state due to " + e.getMessage(), e); - throw new ConcurrentOperationException("Unable to change state due to " + e.getMessage()); - } - } - - @Override - public void migrateWithStorage(final String vmUuid, final long srcHostId, final long destHostId, final Map volumeToPool) - throws ResourceUnavailableException, ConcurrentOperationException { - - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - orchestrateMigrateWithStorage(vmUuid, srcHostId, destHostId, volumeToPool); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - } else { - final Outcome outcome = migrateVmWithStorageThroughJobQueue(vmUuid, srcHostId, destHostId, volumeToPool); - - retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmWithStorage"); - - try { - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (InsufficientCapacityException ex) { - throw new RuntimeException("Unexpected exception", ex); - } - } - } - - private void orchestrateMigrateWithStorage(final String vmUuid, final long srcHostId, final long destHostId, final Map volumeToPool) throws ResourceUnavailableException, - ConcurrentOperationException { - - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - - final HostVO srcHost = _hostDao.findById(srcHostId); - final HostVO destHost = _hostDao.findById(destHostId); - final VirtualMachineGuru vmGuru = getVmGuru(vm); - - final DataCenterVO dc = _dcDao.findById(destHost.getDataCenterId()); - final HostPodVO pod = _podDao.findById(destHost.getPodId()); - final Cluster cluster = _clusterDao.findById(destHost.getClusterId()); - final DeployDestination destination = new DeployDestination(dc, pod, cluster, destHost); - - final VirtualMachineProfile vmSrc = new VirtualMachineProfileImpl(vm); - vmSrc.setHost(srcHost); - for (final NicProfile nic : _networkMgr.getNicProfiles(vm)) { - vmSrc.addNic(nic); - } - - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()), null, null); - profile.setHost(destHost); - - final Map volumeToPoolMap = createMappingVolumeAndStoragePool(profile, destHost, volumeToPool); - - if (volumeToPoolMap == null || volumeToPoolMap.isEmpty()) { - throw new InvalidParameterValueException("Migration of the vm " + vm + "from host " + srcHost + " to destination host " + destHost + - " doesn't involve migrating the volumes."); - } - - AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM_MIGRATE; - if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { - alertType = AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; - } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { - alertType = AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; - } - - _networkMgr.prepareNicForMigration(profile, destination); - volumeMgr.prepareForMigration(profile, destination); - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType()); - final VirtualMachineTO to = hvGuru.implement(profile); - - ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId()); - work.setStep(Step.Prepare); - work.setResourceType(ItWorkVO.ResourceType.Host); - work.setResourceId(destHostId); - work = _workDao.persist(work); - - vm.setLastHostId(srcHostId); - vm.setPodIdToDeployIn(destHost.getPodId()); - moveVmToMigratingState(vm, destHostId, work); - - boolean migrated = false; - try { - Nic defaultNic = _networkModel.getDefaultNic(vm.getId()); - - if (defaultNic != null && VirtualMachine.Type.User.equals(vm.getType())) { - UserVmVO userVm = _userVmDao.findById(vm.getId()); - Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); - userVm.setDetails(details); - - Network network = _networkModel.getNetwork(defaultNic.getNetworkId()); - if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { - final String serviceOffering = _serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()).getDisplayText(); - boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); - List vmData = _networkModel.generateVmData(userVm.getUserData(), userVm.getUserDataDetails(), serviceOffering, vm.getDataCenterId(), vm.getInstanceName(), vm.getHostName(), vm.getId(), - vm.getUuid(), defaultNic.getMacAddress(), userVm.getDetail("SSH.PublicKey"), (String) profile.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows, - VirtualMachineManager.getHypervisorHostname(destination.getHost() != null ? destination.getHost().getName() : "")); - String vmName = vm.getInstanceName(); - String configDriveIsoRootFolder = "/tmp"; - String isoFile = configDriveIsoRootFolder + "/" + vmName + "/configDrive/" + vmName + ".iso"; - profile.setVmData(vmData); - profile.setConfigDriveLabel(VmConfigDriveLabel.value()); - profile.setConfigDriveIsoRootFolder(configDriveIsoRootFolder); - profile.setConfigDriveIsoFile(isoFile); - - AttachOrDettachConfigDriveCommand dettachCommand = new AttachOrDettachConfigDriveCommand(vm.getInstanceName(), vmData, VmConfigDriveLabel.value(), false); - try { - _agentMgr.send(srcHost.getId(), dettachCommand); - logger.debug("Deleted config drive ISO for vm {} in host {}", vm.getInstanceName(), srcHost); - } catch (OperationTimedoutException e) { - logger.error("TIme out occurred while exeuting command AttachOrDettachConfigDrive {}", e.getMessage(), e); - - } - } - } - - volumeMgr.migrateVolumes(vm, to, srcHost, destHost, volumeToPoolMap); - - moveVmOutofMigratingStateOnSuccess(vm, destHost.getId(), work); - - try { - if (!checkVmOnHost(vm, destHostId)) { - logger.error("Vm not found on destination host. Unable to complete migration for {}", vm); - try { - _agentMgr.send(srcHostId, new Commands(cleanup(vm.getInstanceName())), null); - } catch (final AgentUnavailableException e) { - logger.error("AgentUnavailableException while cleanup on source host: {}", srcHost, e); - } - cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true); - throw new CloudRuntimeException("VM not found on destination host. Unable to complete migration for " + vm); - } - } catch (final OperationTimedoutException e) { - logger.error("Error while checking the vm {} is on host {}", vm, destHost, e); - } - migrated = true; - } finally { - if (!migrated) { - logger.info("Migration was unsuccessful. Cleaning up: {}", vm); - _networkMgr.rollbackNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), destHostId); - - _alertMgr.sendAlert(alertType, srcHost.getDataCenterId(), srcHost.getPodId(), - "Unable to migrate vm " + vm.getInstanceName() + " from host " + srcHost.getName() + " in zone " + dc.getName() + " and pod " + dc.getName(), - "Migrate Command failed. Please check logs."); - try { - _agentMgr.send(destHostId, new Commands(cleanup(vm.getInstanceName())), null); - vm.setPodIdToDeployIn(srcHost.getPodId()); - stateTransitTo(vm, Event.OperationFailed, srcHostId); - } catch (final AgentUnavailableException e) { - logger.warn("Looks like the destination Host is unavailable for cleanup.", e); - } catch (final NoTransitionException e) { - logger.error("Error while transitioning vm from migrating to running state.", e); - } - _networkMgr.setHypervisorHostname(profile, destination, false); - } else { - _networkMgr.commitNicForMigration(vmSrc, profile); - volumeMgr.release(vm.getId(), srcHostId); - _networkMgr.setHypervisorHostname(profile, destination, true); - endSnapshotChainForVolumes(volumeToPoolMap, vm.getHypervisorType()); - } - - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } - } - - protected void endSnapshotChainForVolumes(Map volumeToPoolMap, HypervisorType hypervisorType) { - Set volumes = volumeToPoolMap.keySet(); - volumes.forEach(volume -> { - Volume volumeOnDestination = _volsDao.findByPoolIdName(volumeToPoolMap.get(volume).getId(), volume.getName()); - snapshotManager.endSnapshotChainForVolume(volumeOnDestination.getId(), hypervisorType); - }); - } - - protected void recreateCheckpointsKvmOnVmAfterMigration(VMInstanceVO vm, long hostId) { - if (!HypervisorType.KVM.equals(vm.getHypervisorType())) { - logger.debug("Will not recreate checkpoint on VM as it is not running on KVM, thus it is not needed."); - return; - } - - List volumes = getVmVolumesWithCheckpointsToRecreate(vm); - - if (volumes.isEmpty()) { - logger.debug("Will not recreate checkpoints on VM as its volumes do not have any checkpoints associated with them."); - return; - } - - RecreateCheckpointsCommand recreateCheckpointsCommand = new RecreateCheckpointsCommand(volumes, vm.getInstanceName()); - Answer answer = null; - try { - logger.debug(String.format("Recreating the volume checkpoints with URLs [%s] of volumes [%s] on %s as part of the migration process.", volumes.stream().map(VolumeObjectTO::getCheckpointPaths).collect(Collectors.toList()), volumes, vm)); - answer = _agentMgr.send(hostId, recreateCheckpointsCommand); - } catch (AgentUnavailableException | OperationTimedoutException e) { - logger.error(String.format("Exception while sending command to host [%s] to recreate checkpoints with URLs [%s] of volumes [%s] on %s due to: [%s].", hostId, volumes.stream().map(VolumeObjectTO::getCheckpointPaths).collect(Collectors.toList()), volumes, vm, e.getMessage()), e); - throw new CloudRuntimeException(e); - } finally { - if (answer != null && answer.getResult()) { - logger.debug(String.format("Successfully recreated checkpoints on VM [%s].", vm)); - return; - } - - logger.debug(String.format("Migration on VM [%s] was successful; however, we weren't able to recreate the checkpoints on it. Marking the snapshot chain as ended." + - " Next snapshot will create a new snapshot chain.", vm)); - - volumes.forEach(volumeObjectTO -> snapshotManager.endSnapshotChainForVolume(volumeObjectTO.getId(), HypervisorType.KVM)); - } - } - - - protected List getVmVolumesWithCheckpointsToRecreate(VMInstanceVO vm) { - List vmVolumes = _volsDao.findByInstance(vm.getId()); - List volumes = new ArrayList<>(); - - for (VolumeVO volume : vmVolumes) { - Pair, Set> volumeCheckpointPathsAndImageStoreUrls = volumeMgr.getVolumeCheckpointPathsAndImageStoreUrls(volume.getId(), HypervisorType.KVM); - if (volumeCheckpointPathsAndImageStoreUrls.first().isEmpty()) { - continue; - } - VolumeObjectTO volumeTo = new VolumeObjectTO(); - volumeTo.setCheckpointPaths(volumeCheckpointPathsAndImageStoreUrls.first()); - volumeTo.setCheckpointImageStoreUrls(volumeCheckpointPathsAndImageStoreUrls.second()); - volumeTo.setPath(volume.getPath()); - volumes.add(volumeTo); - } - return volumes; - } - - - @Override - public VirtualMachineTO toVmTO(final VirtualMachineProfile profile) { - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(profile.getVirtualMachine().getHypervisorType()); - final VirtualMachineTO to = hvGuru.implement(profile); - return to; - } - - protected void cancelWorkItems(final long nodeId) { - final GlobalLock scanLock = GlobalLock.getInternLock("vmmgr.cancel.workitem"); - - try { - if (scanLock.lock(3)) { - try { - final List works = _workDao.listWorkInProgressFor(nodeId); - for (final ItWorkVO work : works) { - logger.info("Handling unfinished work item: {}", work); - try { - final VMInstanceVO vm = _vmDao.findById(work.getInstanceId()); - if (vm != null) { - if (work.getType() == State.Starting) { - _haMgr.scheduleRestart(vm, true); - work.setManagementServerId(_nodeId); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } else if (work.getType() == State.Stopping) { - _haMgr.scheduleStop(vm, vm.getHostId(), WorkType.CheckStop); - work.setManagementServerId(_nodeId); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } else if (work.getType() == State.Migrating) { - _haMgr.scheduleMigration(vm); - work.setStep(Step.Done); - _workDao.update(work.getId(), work); - } - } - } catch (final Exception e) { - logger.error("Error while handling {}", work, e); - } - } - } finally { - scanLock.unlock(); - } - } - } finally { - scanLock.releaseRef(); - } - } - - @Override - public void migrateAway(final String vmUuid, final long srcHostId) throws InsufficientServerCapacityException { - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - try { - orchestrateMigrateAway(vmUuid, srcHostId, null); - } catch (final InsufficientServerCapacityException e) { - logger.warn("Failed to deploy vm {} with original planner, sending HAPlanner", vmUuid); - orchestrateMigrateAway(vmUuid, srcHostId, _haMgr.getHAPlanner()); - } - } finally { - _workJobDao.expunge(placeHolder.getId()); - } - } else { - final Outcome outcome = migrateVmAwayThroughJobQueue(vmUuid, srcHostId); - - retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmAway"); - - try { - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (ResourceUnavailableException | InsufficientCapacityException ex) { - throw new RuntimeException("Unexpected exception", ex); - } - } - } - - private void orchestrateMigrateAway(final String vmUuid, final long srcHostId, final DeploymentPlanner planner) throws InsufficientServerCapacityException { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - if (vm == null) { - String message = String.format("Unable to find VM with uuid [%s].", vmUuid); - logger.warn(message); - throw new CloudRuntimeException(message); - } - - ServiceOfferingVO offeringVO = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offeringVO, null, null); - - final Long hostId = vm.getHostId(); - if (hostId == null) { - String message = String.format("Unable to migrate %s due to it does not have a host id.", vm.toString()); - logger.warn(message); - throw new CloudRuntimeException(message); - } - - final Host host = _hostDao.findById(hostId); - Long poolId = null; - final List vols = _volsDao.findReadyRootVolumesByInstance(vm.getId()); - for (final VolumeVO rootVolumeOfVm : vols) { - final StoragePoolVO rootDiskPool = _storagePoolDao.findById(rootVolumeOfVm.getPoolId()); - if (rootDiskPool != null) { - poolId = rootDiskPool.getId(); - } - } - - final ExcludeList excludes = new ExcludeList(); - excludes.addHost(hostId); - DataCenterDeployment plan = getMigrationDeployment(vm, host, poolId, excludes); - - DeployDestination dest = null; - while (true) { - - try { - plan.setMigrationPlan(true); - dest = _dpMgr.planDeployment(profile, plan, excludes, planner); - } catch (final AffinityConflictException e2) { - String message = String.format("Unable to create deployment, affinity rules associated to the %s conflict.", vm.toString()); - logger.warn(message, e2); - throw new CloudRuntimeException(message, e2); - } - if (dest == null) { - logger.warn("Unable to find destination for migrating the vm {}", profile); - throw new InsufficientServerCapacityException("Unable to find a server to migrate to.", DataCenter.class, host.getDataCenterId()); - } - logger.debug("Found destination {} for migrating to.", dest); - - excludes.addHost(dest.getHost().getId()); - try { - migrate(vm, srcHostId, dest); - return; - } catch (ResourceUnavailableException | ConcurrentOperationException e) { - logger.warn("Unable to migrate {} to {} due to [{}]", vm.toString(), dest.getHost().toString(), e.getMessage(), e); - } - - try { - advanceStop(vmUuid, true); - throw new CloudRuntimeException("Unable to migrate " + vm); - } catch (final ResourceUnavailableException | ConcurrentOperationException | OperationTimedoutException e) { - logger.error("Unable to stop {} due to [{}].", vm.toString(), e.getMessage(), e); - throw new CloudRuntimeException("Unable to migrate " + vm); - } - } - } - - /** - * Check if the virtual machine has any volume in cluster-wide pool - * @param vmId id of the virtual machine - * @return true if volume exists on cluster-wide pool else false - */ - @Override - public boolean checkIfVmHasClusterWideVolumes(Long vmId) { - final List volumesList = _volsDao.findCreatedByInstance(vmId); - - return volumesList.parallelStream() - .anyMatch(vol -> _storagePoolDao.findById(vol.getPoolId()).getScope().equals(ScopeType.CLUSTER)); - - } - - @Override - public DataCenterDeployment getMigrationDeployment(final VirtualMachine vm, final Host host, final Long poolId, final ExcludeList excludes) { - if (MIGRATE_VM_ACROSS_CLUSTERS.valueIn(host.getDataCenterId()) && - (HypervisorType.VMware.equals(host.getHypervisorType()) || !checkIfVmHasClusterWideVolumes(vm.getId()))) { - logger.info("Searching for hosts in the zone for vm migration"); - List clustersToExclude = _clusterDao.listAllClusterIds(host.getDataCenterId()); - List clusterList = _clusterDao.listByDcHyType(host.getDataCenterId(), host.getHypervisorType().toString()); - for (ClusterVO cluster : clusterList) { - clustersToExclude.remove(cluster.getId()); - } - for (Long clusterId : clustersToExclude) { - excludes.addCluster(clusterId); - } - if (VirtualMachine.systemVMs.contains(vm.getType())) { - return new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), null, null, poolId, null); - } - return new DataCenterDeployment(host.getDataCenterId(), null, null, null, poolId, null); - } - return new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, poolId, null); - } - - protected class CleanupTask extends ManagedContextRunnable { - @Override - protected void runInContext() { - logger.debug("VM Operation Thread Running"); - try { - _workDao.cleanup(VmOpCleanupWait.value()); - final Date cutDate = new Date(DateUtil.currentGMTTime().getTime() - VmOpCleanupInterval.value() * 1000); - _workJobDao.expungeCompletedWorkJobs(cutDate); - } catch (final Exception e) { - logger.error("VM Operations failed due to ", e); - } - } - } - - @Override - public void reboot(final String vmUuid, final Map params) throws InsufficientCapacityException, ResourceUnavailableException { - try { - advanceReboot(vmUuid, params); - } catch (final ConcurrentOperationException e) { - throw new CloudRuntimeException("Unable to reboot a VM due to concurrent operation", e); - } - } - - @Override - public void advanceReboot(final String vmUuid, final Map params) - throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { - - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if ( jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - logger.debug("reboot parameter value of {} == {} at orchestration", VirtualMachineProfile.Param.BootIntoSetup.getName(), - (params == null? "":params.get(VirtualMachineProfile.Param.BootIntoSetup))); - orchestrateReboot(vmUuid, params); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - } else { - logger.debug("reboot parameter value of {} == {} through job-queue", VirtualMachineProfile.Param.BootIntoSetup.getName(), - (params == null? "":params.get(VirtualMachineProfile.Param.BootIntoSetup))); - final Outcome outcome = rebootVmThroughJobQueue(vmUuid, params); - - retrieveVmFromJobOutcome(outcome, vmUuid, "rebootVm"); - - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } - } - - private void orchestrateReboot(final String vmUuid, final Map params) throws InsufficientCapacityException, ConcurrentOperationException, - ResourceUnavailableException { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - if (_vmSnapshotMgr.hasActiveVMSnapshotTasks(vm.getId())) { - logger.error("Unable to reboot Instance: {} due to: {} has active Instance Snapshot tasks", vm, vm.getInstanceName()); - throw new CloudRuntimeException("Unable to reboot Instance: " + vm + " due to: " + vm.getInstanceName() + " has active Instance Snapshots tasks"); - } - final DataCenter dc = _entityMgr.findById(DataCenter.class, vm.getDataCenterId()); - final Host host = _hostDao.findById(vm.getHostId()); - if (host == null) { - throw new CloudRuntimeException("Unable to retrieve host with id " + vm.getHostId()); - } - final Cluster cluster = _entityMgr.findById(Cluster.class, host.getClusterId()); - final Pod pod = _entityMgr.findById(Pod.class, host.getPodId()); - final DeployDestination dest = new DeployDestination(dc, pod, cluster, host); - - try { - final Commands cmds = new Commands(Command.OnError.Stop); - RebootCommand rebootCmd = new RebootCommand(vm.getInstanceName(), getExecuteInSequence(vm.getHypervisorType())); - VirtualMachineTO vmTo = getVmTO(vm.getId()); - checkAndSetEnterSetupMode(vmTo, params); - rebootCmd.setVirtualMachine(vmTo); - updateRebootCommandWithExternalDetails(host, vmTo, rebootCmd); - cmds.addCommand(rebootCmd); - _agentMgr.send(host.getId(), cmds); - - final Answer rebootAnswer = cmds.getAnswer(RebootAnswer.class); - if (rebootAnswer != null && rebootAnswer.getResult()) { - boolean isVmSecurityGroupEnabled = _securityGroupManager.isVmSecurityGroupEnabled(vm.getId()); - if (isVmSecurityGroupEnabled && vm.getType() == VirtualMachine.Type.User) { - List affectedVms = new ArrayList<>(); - affectedVms.add(vm.getId()); - _securityGroupManager.scheduleRulesetUpdateToHosts(affectedVms, true, null); - } - if (vmTo.getGpuDevice() != null) { - _resourceMgr.updateGPUDetailsForVmStart(host.getId(), vm.getId(), vmTo.getGpuDevice()); - } - return; - } - - String errorMsg = "Unable to reboot VM " + vm + " on " + dest.getHost() + " due to " + (rebootAnswer == null ? "no reboot response" : rebootAnswer.getDetails()); - logger.info(errorMsg); - throw new CloudRuntimeException(errorMsg); - } catch (final OperationTimedoutException e) { - logger.warn("Unable to send the reboot command to host {} for the vm {} due to operation timeout.", dest.getHost(), vm, e); - throw new CloudRuntimeException("Failed to reboot the vm on host " + dest.getHost(), e); - } - } - - private void checkAndSetEnterSetupMode(VirtualMachineTO vmTo, Map params) { - Boolean enterSetup = null; - if (params != null) { - enterSetup = (Boolean) params.get(VirtualMachineProfile.Param.BootIntoSetup); - } - logger.debug("Orchestrating VM reboot for '{}' {} set to {}", vmTo.getName(), VirtualMachineProfile.Param.BootIntoSetup, enterSetup); - vmTo.setEnterHardwareSetup(enterSetup == null ? false : enterSetup); - } - - /** - * This method helps constructing vmSpec for Unmanage operation for Stopped Instance - * @param vmId - * @param hostId - * @return VirtualMachineTO - */ - protected VirtualMachineTO prepVmSpecForUnmanageCmd(Long vmId, Long hostId) { - final VMInstanceVO vm = _vmDao.findById(vmId); - final Account owner = _entityMgr.findById(Account.class, vm.getAccountId()); - final ServiceOfferingVO offering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); - final VirtualMachineTemplate template = _entityMgr.findByIdIncludingRemoved(VirtualMachineTemplate.class, vm.getTemplateId()); - Host host = _hostDao.findById(hostId); - VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, template, offering, owner, null); - updateOverCommitRatioForVmProfile(vmProfile, host.getClusterId()); - final List nics = _nicsDao.listByVmId(vmProfile.getId()); - Collections.sort(nics, (nic1, nic2) -> { - Long nicId1 = Long.valueOf(nic1.getDeviceId()); - Long nicId2 = Long.valueOf(nic2.getDeviceId()); - return nicId1.compareTo(nicId2); - }); - - for (final NicVO nic : nics) { - final Network network = _networkModel.getNetwork(nic.getNetworkId()); - final NicProfile nicProfile = - new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), - _networkModel.getNetworkTag(vmProfile.getHypervisorType(), network)); - vmProfile.addNic(nicProfile); - } - - List volumes = _volsDao.findUsableVolumesForInstance(vmId); - for (VolumeVO vol: volumes) { - VolumeInfo volumeInfo = volumeDataFactory.getVolume(vol.getId()); - DataTO dataTO = volumeInfo.getTO(); - DiskTO disk = storageMgr.getDiskWithThrottling(dataTO, vol.getVolumeType(), vol.getDeviceId(), vol.getPath(), vm.getServiceOfferingId(), vol.getDiskOfferingId()); - vmProfile.addDisk(disk); - } - - Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId, - List.of(VirtualMachineProfile.Param.BootType.getName(), VirtualMachineProfile.Param.BootMode.getName(), - VirtualMachineProfile.Param.UefiFlag.getName())); - - if (details.containsKey(VirtualMachineProfile.Param.BootType.getName())) { - vmProfile.getParameters().put(VirtualMachineProfile.Param.BootType, details.get(VirtualMachineProfile.Param.BootType.getName())); - } - - if (details.containsKey(VirtualMachineProfile.Param.BootMode.getName())) { - vmProfile.getParameters().put(VirtualMachineProfile.Param.BootMode, details.get(VirtualMachineProfile.Param.BootMode.getName())); - } - - if (details.containsKey(VirtualMachineProfile.Param.UefiFlag.getName())) { - vmProfile.getParameters().put(VirtualMachineProfile.Param.UefiFlag, details.get(VirtualMachineProfile.Param.UefiFlag.getName())); - } - - return toVmTO(vmProfile); - } - - protected VirtualMachineTO getVmTO(Long vmId) { - final VMInstanceVO vm = _vmDao.findById(vmId); - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - final List nics = _nicsDao.listByVmId(profile.getId()); - Collections.sort(nics, new Comparator() { - @Override - public int compare(NicVO nic1, NicVO nic2) { - Long nicId1 = Long.valueOf(nic1.getDeviceId()); - Long nicId2 = Long.valueOf(nic2.getDeviceId()); - return nicId1.compareTo(nicId2); - } - }); - - for (final NicVO nic : nics) { - final Network network = _networkModel.getNetwork(nic.getNetworkId()); - final NicProfile nicProfile = - new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), - _networkModel.getNetworkTag(profile.getHypervisorType(), network)); - profile.addNic(nicProfile); - } - final VirtualMachineTO to = toVmTO(profile); - return to; - } - - public Command cleanup(final VirtualMachine vm, Map dpdkInterfaceMapping) { - StopCommand cmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), false); - cmd.setControlIp(getControlNicIpForVM(vm)); - if (MapUtils.isNotEmpty(dpdkInterfaceMapping)) { - cmd.setDpdkInterfaceMapping(dpdkInterfaceMapping); - } - Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - cmd.setVlanToPersistenceMap(vlanToPersistenceMap); - } - return cmd; - } - - private String getControlNicIpForVM(VirtualMachine vm) { - if (null == vm.getType()) { - return null; - } - - switch (vm.getType()) { - case ConsoleProxy: - case SecondaryStorageVm: - NicVO nic = _nicsDao.getControlNicForVM(vm.getId()); - return nic.getIPv4Address(); - case DomainRouter: - return vm.getPrivateIpAddress(); - default: - logger.debug("{} is a [{}], returning null for control Nic IP.", vm.toString(), vm.getType()); - return null; - } - } - public Command cleanup(final String vmName) { - VirtualMachine vm = _vmDao.findVMByInstanceName(vmName); - - StopCommand cmd = new StopCommand(vmName, getExecuteInSequence(null), false); - cmd.setControlIp(getControlNicIpForVM(vm)); - Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - cmd.setVlanToPersistenceMap(vlanToPersistenceMap); - } - return cmd; - } - - public void syncVMMetaData(final Map vmMetadatum) { - if (vmMetadatum == null || vmMetadatum.isEmpty()) { - return; - } - List, Pair>> vmDetails = _userVmDao.getVmsDetailByNames(vmMetadatum.keySet(), "platform"); - for (final Map.Entry entry : vmMetadatum.entrySet()) { - final String name = entry.getKey(); - final String platform = entry.getValue(); - if (platform == null || platform.isEmpty()) { - continue; - } - - boolean found = false; - for(Pair, Pair> vmDetail : vmDetails ) { - Pair vmNameTypePair = vmDetail.first(); - if(vmNameTypePair.first().equals(name)) { - found = true; - if(vmNameTypePair.second() == VirtualMachine.Type.User) { - Pair detailPair = vmDetail.second(); - String platformDetail = detailPair.second(); - - if (platformDetail != null && platformDetail.equals(platform)) { - break; - } - updateVmMetaData(detailPair.first(), platform); - } - break; - } - } - - if(!found) { - VMInstanceVO vm = _vmDao.findVMByInstanceName(name); - if(vm != null && vm.getType() == VirtualMachine.Type.User) { - updateVmMetaData(vm.getId(), platform); - } - } - } - } - - private void updateVmMetaData(Long vmId, String platform) { - UserVmVO userVm = _userVmDao.findById(vmId); - _userVmDao.loadDetails(userVm); - if ( userVm.details.containsKey(VmDetailConstants.TIME_OFFSET)) { - userVm.details.remove(VmDetailConstants.TIME_OFFSET); - } - userVm.setDetail(VmDetailConstants.PLATFORM, platform); - String pvdriver = "xenserver56"; - if ( platform.contains("device_id")) { - pvdriver = "xenserver61"; - } - if (!userVm.details.containsKey(VmDetailConstants.HYPERVISOR_TOOLS_VERSION) || !userVm.details.get(VmDetailConstants.HYPERVISOR_TOOLS_VERSION).equals(pvdriver)) { - userVm.setDetail(VmDetailConstants.HYPERVISOR_TOOLS_VERSION, pvdriver); - } - _userVmDao.saveDetails(userVm); - } - - @Override - public boolean isRecurring() { - return true; - } - - @Override - public boolean processAnswers(final long agentId, final long seq, final Answer[] answers) { - for (final Answer answer : answers) { - if ( answer instanceof ClusterVMMetaDataSyncAnswer) { - final ClusterVMMetaDataSyncAnswer cvms = (ClusterVMMetaDataSyncAnswer)answer; - if (!cvms.isExecuted()) { - syncVMMetaData(cvms.getVMMetaDatum()); - cvms.setExecuted(); - } - } - } - return true; - } - - @Override - public boolean processTimeout(final long agentId, final long seq) { - return true; - } - - @Override - public int getTimeout() { - return -1; - } - - @Override - public boolean processCommands(final long agentId, final long seq, final Command[] cmds) { - boolean processed = false; - for (final Command cmd : cmds) { - if (cmd instanceof PingRoutingCommand) { - final PingRoutingCommand ping = (PingRoutingCommand)cmd; - if (ping.getHostVmStateReport() != null) { - _syncMgr.processHostVmStatePingReport(agentId, ping.getHostVmStateReport(), ping.getOutOfBand()); - } - scanStalledVMInTransitionStateOnUpHost(agentId); - processed = true; - } - } - return processed; - } - - @Override - public AgentControlAnswer processControlCommand(final long agentId, final AgentControlCommand cmd) { - return null; - } - - @Override - public boolean processDisconnect(final long agentId, final Status state) { - return true; - } - - @Override - public void processHostAboutToBeRemoved(long hostId) { - } - - @Override - public void processHostRemoved(long hostId, long clusterId) { - } - - @Override - public void processHostAdded(long hostId) { - } - - @Override - public void processConnect(final Host agent, final StartupCommand cmd, final boolean forRebalance) throws ConnectionException { - if (!(cmd instanceof StartupRoutingCommand)) { - return; - } - - logger.debug("Received startup command from hypervisor host. host: {}", agent); - - _syncMgr.resetHostSyncState(agent); - - if (forRebalance) { - logger.debug("Not processing listener {} as connect happens on rebalance process", this); - return; - } - final Long clusterId = agent.getClusterId(); - final long agentId = agent.getId(); - - if (agent.getHypervisorType() == HypervisorType.XenServer) { - final ClusterVMMetaDataSyncCommand syncVMMetaDataCmd = new ClusterVMMetaDataSyncCommand(ClusterVMMetaDataSyncInterval.value(), clusterId); - try { - final long seq_no = _agentMgr.send(agentId, new Commands(syncVMMetaDataCmd), this); - logger.debug("Cluster VM metadata sync started with jobid {}", seq_no); - } catch (final AgentUnavailableException e) { - logger.fatal("The Cluster VM metadata sync process failed for cluster {} with {}", _clusterDao.findById(clusterId), e); - } - } - } - - protected class TransitionTask extends ManagedContextRunnable { - @Override - protected void runInContext() { - final GlobalLock lock = GlobalLock.getInternLock("TransitionChecking"); - if (lock == null) { - logger.debug("Couldn't get the global lock"); - return; - } - - if (!lock.lock(30)) { - logger.debug("Couldn't lock the db"); - return; - } - try { - scanStalledVMInTransitionStateOnDisconnectedHosts(); - - final List instances = _vmDao.findVMInTransition(new Date(DateUtil.currentGMTTime().getTime() - AgentManager.Wait.value() * 1000), State.Starting, State.Stopping); - for (final VMInstanceVO instance : instances) { - final State state = instance.getState(); - if (state == State.Stopping) { - _haMgr.scheduleStop(instance, instance.getHostId(), WorkType.CheckStop); - } else if (state == State.Starting) { - _haMgr.scheduleRestart(instance, true); - } - } - } catch (final Exception e) { - logger.warn("Caught the following exception on transition checking", e); - } finally { - lock.unlock(); - } - } - } - - @Override - public VMInstanceVO findById(final long vmId) { - return _vmDao.findById(vmId); - } - - @Override - public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffering newServiceOffering) { - if (newServiceOffering == null) { - throw new InvalidParameterValueException("Invalid parameter, newServiceOffering can't be null"); - } - - if (ServiceOffering.State.Inactive.equals(newServiceOffering.getState())) { - throw new InvalidParameterValueException(String.format("New service offering is inactive: [%s].", newServiceOffering.getUuid())); - } - - if (!(vmInstance.getState().equals(State.Stopped) || vmInstance.getState().equals(State.Running))) { - logger.warn("Unable to upgrade virtual machine {} in state {}", vmInstance.toString(), vmInstance.getState()); - throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() + " " + " in state " + vmInstance.getState() + - "; make sure the virtual machine is stopped/running"); - } - - if (!newServiceOffering.isDynamic() && vmInstance.getServiceOfferingId() == newServiceOffering.getId()) { - logger.info("Not upgrading vm {} since it already has the requested service offering ({})", vmInstance.toString(), newServiceOffering.getName()); - - throw new InvalidParameterValueException("Not upgrading vm " + vmInstance.toString() + " since it already " + "has the requested service offering (" + - newServiceOffering.getName() + ")"); - } - - final ServiceOfferingVO currentServiceOffering = _offeringDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); - final DiskOfferingVO currentDiskOffering = _diskOfferingDao.findByIdIncludingRemoved(currentServiceOffering.getDiskOfferingId()); - final DiskOfferingVO newDiskOffering = _diskOfferingDao.findById(newServiceOffering.getDiskOfferingId()); - - checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newDiskOffering); - - if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) { - throw new InvalidParameterValueException("isSystem property is different for current service offering and new service offering"); - } - - final List currentTags = StringUtils.csvTagsToList(currentDiskOffering.getTags()); - final List newTags = StringUtils.csvTagsToList(newDiskOffering.getTags()); - if (VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(vmInstance.getDataCenterId())) { - if (!VolumeApiServiceImpl.doesNewDiskOfferingHasTagsAsOldDiskOffering(currentDiskOffering, newDiskOffering)) { - throw new InvalidParameterValueException("Unable to upgrade virtual machine; the current service offering " + " should have tags as subset of " + - "the new service offering tags. Current service offering tags: " + currentTags + "; " + "new service " + "offering tags: " + newTags); - } - } - } - - /** - * Throws an InvalidParameterValueException in case the new service offerings does not match the storage scope (e.g. local or shared). - */ - protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, DiskOffering newDiskOffering) { - boolean isRootVolumeOnLocalStorage = isRootVolumeOnLocalStorage(vmInstance.getId()); - - if (newDiskOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) { - String message = String .format("Unable to upgrade virtual machine %s, target offering use local storage but the storage pool where " - + "the volume is allocated is a shared storage.", vmInstance.toString()); - throw new InvalidParameterValueException(message); - } - - if (!newDiskOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) { - String message = String.format("Unable to upgrade virtual machine %s, target offering use shared storage but the storage pool where " - + "the volume is allocated is a local storage.", vmInstance.toString()); - throw new InvalidParameterValueException(message); - } - } - - public boolean isRootVolumeOnLocalStorage(long vmId) { - ScopeType poolScope = ScopeType.ZONE; - List volumes = _volsDao.findByInstanceAndType(vmId, Type.ROOT); - if(CollectionUtils.isNotEmpty(volumes)) { - VolumeVO rootDisk = volumes.get(0); - Long poolId = rootDisk.getPoolId(); - if (poolId != null) { - StoragePoolVO storagePoolVO = _storagePoolDao.findById(poolId); - poolScope = storagePoolVO.getScope(); - } - } - return ScopeType.HOST == poolScope; - } - - @Override - public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) { - - final VMInstanceVO vmForUpdate = _vmDao.findById(vmId); - vmForUpdate.setServiceOfferingId(newServiceOffering.getId()); - final ServiceOffering newSvcOff = _entityMgr.findById(ServiceOffering.class, newServiceOffering.getId()); - vmForUpdate.setHaEnabled(newSvcOff.isOfferHA()); - vmForUpdate.setLimitCpuUse(newSvcOff.getLimitCpuUse()); - vmForUpdate.setServiceOfferingId(newSvcOff.getId()); - if (newServiceOffering.isDynamic()) { - saveCustomOfferingDetails(vmId, newServiceOffering); - } - if (currentServiceOffering.isDynamic() && !newServiceOffering.isDynamic()) { - removeCustomOfferingDetails(vmId); - } - VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmForUpdate.getTemplateId()); - boolean dynamicScalingEnabled = _userVmMgr.checkIfDynamicScalingCanBeEnabled(vmForUpdate, newServiceOffering, template, vmForUpdate.getDataCenterId()); - vmForUpdate.setDynamicallyScalable(dynamicScalingEnabled); - return _vmDao.update(vmId, vmForUpdate); - } - - @Override - public NicProfile addVmToNetwork(final VirtualMachine vm, final Network network, final NicProfile requested) - throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId(), network.getUuid()); - try { - return orchestrateAddVmToNetwork(vm, network, requested); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - } else { - final Outcome outcome = addVmToNetworkThroughJobQueue(vm, network, requested); - - retrieveVmFromJobOutcome(outcome, vm.getUuid(), "addVmToNetwork"); - - Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - - if (jobResult != null && jobResult instanceof NicProfile) { - return (NicProfile) jobResult; - } - - throw new RuntimeException("null job execution result"); - } - } - - /** - * duplicated in {@see UserVmManagerImpl} for a {@see UserVmVO} - */ - private void checkIfNetworkExistsForUserVM(VirtualMachine virtualMachine, Network network) { - if (virtualMachine.getType() != VirtualMachine.Type.User) { - return; // others may have multiple nics in the same network - } - List allNics = _nicsDao.listByVmId(virtualMachine.getId()); - for (NicVO nic : allNics) { - if (nic.getNetworkId() == network.getId()) { - throw new CloudRuntimeException("A NIC already exists for VM:" + virtualMachine.getInstanceName() + " in network: " + network.getUuid()); - } - } - } - - private NicProfile orchestrateAddVmToNetwork(final VirtualMachine vm, final Network network, final NicProfile requested) throws ConcurrentOperationException, ResourceUnavailableException, - InsufficientCapacityException { - final CallContext cctx = CallContext.current(); - - checkIfNetworkExistsForUserVM(vm, network); - logger.debug("Adding Instance {} to Network {}; requested NIC profile {}", vm, network, requested); - final VMInstanceVO vmVO = _vmDao.findById(vm.getId()); - final ReservationContext context = new ReservationContextImpl(null, null, cctx.getCallingUser(), cctx.getCallingAccount()); - - final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); - - final DataCenter dc = _entityMgr.findById(DataCenter.class, network.getDataCenterId()); - final Host host = _hostDao.findById(vm.getHostId()); - final DeployDestination dest = new DeployDestination(dc, null, null, host); - - if (vm.getState() == State.Running) { - final NicProfile nic = _networkMgr.createNicForVm(network, requested, context, vmProfile, true); - - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); - final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); - - final NicTO nicTO = toNicTO(nic, vmProfile.getVirtualMachine().getHypervisorType()); - - //4) plug the nic to the vm - logger.debug("Plugging NIC for Instance {} in Network {}", vm, network); - - boolean result = false; - try { - result = plugNic(network, nicTO, vmTO, context, dest); - if (result) { - _userVmMgr.setupVmForPvlan(true, vm.getHostId(), nic); - logger.debug("Nic is plugged successfully for vm {} in network {}. VM is a part of network now.", vm, network); - final long isDefault = nic.isDefaultNic() ? 1 : 0; - - if(VirtualMachine.Type.User.equals(vmVO.getType())) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vmVO.getAccountId(), vmVO.getDataCenterId(), vmVO.getId(), - Long.toString(nic.getId()), network.getNetworkOfferingId(), null, isDefault, VirtualMachine.class.getName(), vmVO.getUuid(), vm.isDisplay()); - } - return nic; - } else { - logger.warn("Failed to plug NIC to the Instance {} in Network {}", vm, network); - return null; - } - } finally { - if (!result) { - logger.debug("Removing NIC {} from Instance {} as NIC plug failed on the backend.", nic, vmProfile.getVirtualMachine()); - _networkMgr.removeNic(vmProfile, _nicsDao.findById(nic.getId())); - } - } - } else if (vm.getState() == State.Stopped) { - return _networkMgr.createNicForVm(network, requested, context, vmProfile, false); - } else { - logger.warn("Unable to add vm {} to network {}", vm, network); - throw new ResourceUnavailableException("Unable to add vm " + vm + " to network, is not in the right state", DataCenter.class, vm.getDataCenterId()); - } - } - - @Override - public NicTO toNicTO(final NicProfile nic, final HypervisorType hypervisorType) { - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(hypervisorType); - return hvGuru.toNicTO(nic); - } - - @Override - public boolean removeNicFromVm(final VirtualMachine vm, final Nic nic) - throws ConcurrentOperationException, ResourceUnavailableException { - - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - return orchestrateRemoveNicFromVm(vm, nic); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - - } else { - final Outcome outcome = removeNicFromVmThroughJobQueue(vm, nic); - - retrieveVmFromJobOutcome(outcome, vm.getUuid(), "removeNicFromVm"); - - try { - Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - if (jobResult != null && jobResult instanceof Boolean) { - return (Boolean) jobResult; - } - } catch (InsufficientCapacityException ex) { - throw new RuntimeException("Unexpected exception", ex); - } - - throw new RuntimeException("Job failed with un-handled exception"); - } - } - - private boolean orchestrateRemoveNicFromVm(final VirtualMachine vm, final Nic nic) throws ConcurrentOperationException, ResourceUnavailableException { - final CallContext cctx = CallContext.current(); - final VMInstanceVO vmVO = _vmDao.findById(vm.getId()); - final NetworkVO network = _networkDao.findById(nic.getNetworkId()); - final ReservationContext context = new ReservationContextImpl(null, null, cctx.getCallingUser(), cctx.getCallingAccount()); - - final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); - - final DataCenter dc = _entityMgr.findById(DataCenter.class, network.getDataCenterId()); - final Host host = _hostDao.findById(vm.getHostId()); - final DeployDestination dest = new DeployDestination(dc, null, null, host); - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); - final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); - - final NicProfile nicProfile = - new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), _networkModel.getNetworkRate(network.getId(), vm.getId()), - _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vmProfile.getVirtualMachine().getHypervisorType(), network)); - - if (vm.getState() == State.Running) { - final NicTO nicTO = toNicTO(nicProfile, vmProfile.getVirtualMachine().getHypervisorType()); - logger.debug("Un-plugging NIC {} for Instance {} from Network {}.", nic, vm, network); - final boolean result = unplugNic(network, nicTO, vmTO, context, dest); - if (result) { - _userVmMgr.setupVmForPvlan(false, vm.getHostId(), nicProfile); - logger.debug("NIC is unplugged successfully for Instance {} in Network {}.", vm, network); - final long isDefault = nic.isDefaultNic() ? 1 : 0; - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), - Long.toString(nic.getId()), network.getNetworkOfferingId(), null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); - } else { - logger.warn("Failed to unplug NIC for the Instance {} from Network {}.", vm, network); - return false; - } - } else if (vm.getState() != State.Stopped) { - logger.warn("Unable to remove Instance {} from Network {}", vm, network); - throw new ResourceUnavailableException("Unable to remove Instance " + vm + " from Network, is not in the right state", DataCenter.class, vm.getDataCenterId()); - } - - _networkMgr.releaseNic(vmProfile, nic); - logger.debug("Successfully released NIC {} for Instance {}", nic, vm); - - _networkMgr.removeNic(vmProfile, nic); - _nicsDao.remove(nic.getId()); - return true; - } - - @Override - @DB - public boolean removeVmFromNetwork(final VirtualMachine vm, final Network network, final URI broadcastUri) throws ConcurrentOperationException, ResourceUnavailableException { - return orchestrateRemoveVmFromNetwork(vm, network, broadcastUri); - } - - @DB - private boolean orchestrateRemoveVmFromNetwork(final VirtualMachine vm, final Network network, final URI broadcastUri) throws ConcurrentOperationException, ResourceUnavailableException { - final CallContext cctx = CallContext.current(); - final VMInstanceVO vmVO = _vmDao.findById(vm.getId()); - final ReservationContext context = new ReservationContextImpl(null, null, cctx.getCallingUser(), cctx.getCallingAccount()); - - final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); - - final DataCenter dc = _entityMgr.findById(DataCenter.class, network.getDataCenterId()); - final Host host = _hostDao.findById(vm.getHostId()); - final DeployDestination dest = new DeployDestination(dc, null, null, host); - final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); - final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); - - Nic nic = null; - if (broadcastUri != null) { - nic = _nicsDao.findByNetworkIdInstanceIdAndBroadcastUri(network.getId(), vm.getId(), broadcastUri.toString()); - } else { - nic = _networkModel.getNicInNetwork(vm.getId(), network.getId()); - } - - if (nic == null) { - logger.warn("Could not get a NIC with {}", network); - return false; - } - - if (nic.isDefaultNic() && vm.getType() == VirtualMachine.Type.User) { - logger.warn("Failed to remove NIC from {} in {}, NIC is default.", vm, network); - throw new CloudRuntimeException("Failed to remove NIC from " + vm + " in " + network + ", NIC is default."); - } - - final Nic lock = _nicsDao.acquireInLockTable(nic.getId()); - if (lock == null) { - if (_nicsDao.findById(nic.getId()) == null) { - logger.debug("Not need to remove the vm {} from network {} as the vm doesn't have nic in this network.", vm, network); - return true; - } - throw new ConcurrentOperationException(String.format("Unable to lock nic %s", nic)); - } - - logger.debug("Lock is acquired for nic {} as a part of remove vm {} from network {}", lock, vm, network); - - try { - final NicProfile nicProfile = - new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), _networkModel.getNetworkRate(network.getId(), vm.getId()), - _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vmProfile.getVirtualMachine().getHypervisorType(), network)); - - if (vm.getState() == State.Running) { - final NicTO nicTO = toNicTO(nicProfile, vmProfile.getVirtualMachine().getHypervisorType()); - logger.debug("Un-plugging nic for vm {} from network {}", vm, network); - final boolean result = unplugNic(network, nicTO, vmTO, context, dest); - if (result) { - logger.debug("Nic is unplugged successfully for vm {} in network {}", vm, network); - } else { - logger.warn("Failed to unplug nic for the vm {} from network {}", vm, network); - return false; - } - } else if (vm.getState() != State.Stopped) { - logger.warn("Unable to remove vm {} from network {}", vm, network); - throw new ResourceUnavailableException("Unable to remove vm " + vm + " from network, is not in the right state", DataCenter.class, vm.getDataCenterId()); - } - - _networkMgr.releaseNic(vmProfile, nic); - logger.debug("Successfully released nic {} for vm {}", nic, vm); - - _networkMgr.removeNic(vmProfile, nic); - return true; - } finally { - _nicsDao.releaseFromLockTable(lock.getId()); - logger.debug("Lock is released for nic {} as a part of remove vm {} from network {}", lock, vm, network); - } - } - - @Override - public void findHostAndMigrate(final String vmUuid, final Long newSvcOfferingId, final Map customParameters, final ExcludeList excludes) throws InsufficientCapacityException, ConcurrentOperationException, - ResourceUnavailableException { - - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - if (vm == null) { - throw new CloudRuntimeException("Unable to find " + vmUuid); - } - ServiceOfferingVO newServiceOffering = _offeringDao.findById(newSvcOfferingId); - if (newServiceOffering.isDynamic()) { - newServiceOffering.setDynamicFlag(true); - newServiceOffering = _offeringDao.getComputeOffering(newServiceOffering, customParameters); - } - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, newServiceOffering, null, null); - - final Long srcHostId = vm.getHostId(); - final Long oldSvcOfferingId = vm.getServiceOfferingId(); - if (srcHostId == null) { - throw new CloudRuntimeException("Unable to scale the vm because it doesn't have a host id"); - } - final Host host = _hostDao.findById(srcHostId); - final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, null, null); - excludes.addHost(vm.getHostId()); - vm.setServiceOfferingId(newSvcOfferingId); - - DeployDestination dest = null; - - try { - dest = _dpMgr.planDeployment(profile, plan, excludes, null); - } catch (final AffinityConflictException e2) { - String message = String.format("Unable to create deployment, affinity rules associated to the %s conflict.", vm.toString()); - logger.warn(message, e2); - throw new CloudRuntimeException(message); - } - - if (dest != null) { - logger.debug("Found {} for scaling the vm to.", dest); - } - - if (dest == null) { - throw new InsufficientServerCapacityException("Unable to find a server to scale the vm to.", host.getClusterId()); - } - - excludes.addHost(dest.getHost().getId()); - try { - migrateForScale(vm.getUuid(), srcHostId, dest, oldSvcOfferingId); - } catch (ResourceUnavailableException | ConcurrentOperationException e) { - logger.warn("Unable to migrate {} to {} due to [{}]", vm.toString(), dest.getHost().toString(), e.getMessage(), e); - throw e; - } - } - - @Override - public void migrateForScale(final String vmUuid, final long srcHostId, final DeployDestination dest, final Long oldSvcOfferingId) - throws ResourceUnavailableException, ConcurrentOperationException { - final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); - if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); - try { - orchestrateMigrateForScale(vmUuid, srcHostId, dest, oldSvcOfferingId); - } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } - } - } else { - final Outcome outcome = migrateVmForScaleThroughJobQueue(vmUuid, srcHostId, dest, oldSvcOfferingId); - - retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmForScale"); - - try { - retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (InsufficientCapacityException ex) { - throw new RuntimeException("Unexpected exception", ex); - } - } - } - - private void orchestrateMigrateForScale(final String vmUuid, final long srcHostId, final DeployDestination dest, final Long oldSvcOfferingId) - throws ResourceUnavailableException, ConcurrentOperationException { - - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - logger.info("Migrating {} to {}", vm, dest); - - vm.getServiceOfferingId(); - final long dstHostId = dest.getHost().getId(); - final Host fromHost = _hostDao.findById(srcHostId); - if (fromHost == null) { - String logMessageUnableToFindHost = String.format("Unable to find host to migrate from %s.", srcHostId); - logger.info(logMessageUnableToFindHost); - throw new CloudRuntimeException(logMessageUnableToFindHost); - } - - Host dstHost = _hostDao.findById(dstHostId); - long destHostClusterId = dest.getCluster().getId(); - long fromHostClusterId = fromHost.getClusterId(); - if (fromHostClusterId != destHostClusterId) { - String logMessageHostsOnDifferentCluster = String.format("Source and destination host are not in same cluster, unable to migrate to %s", fromHost); - logger.info(logMessageHostsOnDifferentCluster); - throw new CloudRuntimeException(logMessageHostsOnDifferentCluster); - } - - final VirtualMachineGuru vmGuru = getVmGuru(vm); - - vm = _vmDao.findByUuid(vmUuid); - if (vm == null) { - String message = String.format("Unable to find VM {\"uuid\": \"%s\"}.", vmUuid); - logger.warn(message); - throw new CloudRuntimeException(message); - } - - if (vm.getState() != State.Running) { - String message = String.format("%s is not in \"Running\" state, unable to migrate it. Current state [%s].", vm.toString(), vm.getState()); - logger.warn(message); - throw new CloudRuntimeException(message); - } - - AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM_MIGRATE; - if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { - alertType = AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; - } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { - alertType = AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; - } + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()), null, null); + profile.setHost(dest.getHost()); - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); _networkMgr.prepareNicForMigration(profile, dest); - volumeMgr.prepareForMigration(profile, dest); + profile.setConfigDriveLabel(VmConfigDriveLabel.value()); + updateOverCommitRatioForVmProfile(profile, dest.getHost().getClusterId()); final VirtualMachineTO to = toVmTO(profile); final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to); + setVmNetworkDetails(vm, to); ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId()); work.setStep(Step.Prepare); @@ -4928,98 +1640,153 @@ private void orchestrateMigrateForScale(final String vmUuid, final long srcHostI pfma = _agentMgr.send(dstHostId, pfmc); if (pfma == null || !pfma.getResult()) { final String details = pfma != null ? pfma.getDetails() : "null answer returned"; + final String msg = "Unable to prepare for migration due to " + details; + logger.error("Failed to prepare destination host {} for migration of VM {} : {}", dstHostId, vm.getInstanceName(), details); pfma = null; - throw new AgentUnavailableException(String.format("Unable to prepare for migration to destination host [%s] due to [%s].", dest.getHost(), details), dstHostId); + throw new AgentUnavailableException(msg, dstHostId); } + logger.debug("Successfully prepared destination host {} for migration of VM {} ", dstHostId, vm.getInstanceName()); } catch (final OperationTimedoutException e1) { throw new AgentUnavailableException("Operation timed out", dstHostId); } finally { if (pfma == null) { + _networkMgr.rollbackNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), dstHostId); work.setStep(Step.Done); _workDao.update(work.getId(), work); } } vm.setLastHostId(srcHostId); + _vmDao.resetVmPowerStateTracking(vm.getId()); try { if (vm.getHostId() == null || vm.getHostId() != srcHostId || !changeState(vm, Event.MigrationRequested, dstHostId, work, Step.Migrating)) { - String message = String.format("Migration of %s cancelled because state has changed.", vm.toString()); - logger.warn(message); - throw new ConcurrentOperationException(message); + _networkMgr.rollbackNicForMigration(vmSrc, profile); + if (vm != null) { + volumeMgr.release(vm.getId(), dstHostId); + } + + String msg = "Migration cancelled because state has changed: " + vm; + logger.warn(msg); + throw new ConcurrentOperationException(msg); } } catch (final NoTransitionException e1) { - String message = String.format("Migration of %s cancelled due to [%s].", vm.toString(), e1.getMessage()); - logger.error(message, e1); - throw new ConcurrentOperationException(message); + _networkMgr.rollbackNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), dstHostId); + String msg = String.format("Migration cancelled for VM %s due to state transition failure: %s", + vm.getInstanceName(), e1.getMessage()); + logger.warn(msg, e1); + throw new ConcurrentOperationException("Migration cancelled because " + e1.getMessage()); + } catch (final CloudRuntimeException e2) { + _networkMgr.rollbackNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), dstHostId); + String msg = String.format("Migration cancelled for VM %s due to runtime exception: %s", + vm.getInstanceName(), e2.getMessage()); + logger.error(msg, e2); + work.setStep(Step.Done); + _workDao.update(work.getId(), work); + try { + stateTransitTo(vm, Event.OperationFailed, srcHostId); + } catch (final NoTransitionException e3) { + logger.warn(e3.getMessage()); + } + throw new CloudRuntimeException("Migration cancelled because " + e2.getMessage()); } boolean migrated = false; + Map dpdkInterfaceMapping = new HashMap<>(); try { - final MigrateCommand mc = buildMigrateCommand(vm, to, dest, pfma, null); + final MigrateCommand mc = buildMigrateCommand(vm, to, dest, pfma, dpdkInterfaceMapping); try { final Answer ma = _agentMgr.send(vm.getLastHostId(), mc); if (ma == null || !ma.getResult()) { - String msg = String.format("Unable to migrate %s due to [%s].", vm.toString(), ma != null ? ma.getDetails() : "null answer returned"); + final String details = ma != null ? ma.getDetails() : "null answer returned"; + String msg = String.format("Migration command failed for VM %s on source host id=%s to destination host %s: %s", + vm.getInstanceName(), vm.getLastHostId(), dstHostId, details); logger.error(msg); - throw new CloudRuntimeException(msg); + throw new CloudRuntimeException(details); } + logger.info("Migration command successful for VM {}", vm.getInstanceName()); } catch (final OperationTimedoutException e) { - if (e.isActive()) { - logger.warn("Active migration command so scheduling a restart for {}", vm, e); - _haMgr.scheduleRestart(vm, true); + boolean success = false; + if (HypervisorType.KVM.equals(vm.getHypervisorType())) { + try { + final Answer answer = _agentMgr.send(vm.getHostId(), new CheckVirtualMachineCommand(vm.getInstanceName())); + if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) { + final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer) answer; + if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) { + logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, vm.getHostId())); + success = true; + } + } + } catch (Exception ex) { + logger.error(String.format("Failed to get state of VM %s on destination host %s: %s", vm, vm.getHostId(), ex.getMessage())); + } + } + if (!success) { + if (e.isActive()) { + logger.warn("Active migration command so scheduling a restart for {}", vm, e); + _haMgr.scheduleRestart(vm, true); + + throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId); + } } - throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId, e); } try { - final long newServiceOfferingId = vm.getServiceOfferingId(); - vm.setServiceOfferingId(oldSvcOfferingId); if (!changeState(vm, VirtualMachine.Event.OperationSucceeded, dstHostId, work, Step.Started)) { throw new ConcurrentOperationException("Unable to change the state for " + vm); } - vm.setServiceOfferingId(newServiceOfferingId); } catch (final NoTransitionException e1) { throw new ConcurrentOperationException("Unable to change state due to " + e1.getMessage()); } try { if (!checkVmOnHost(vm, dstHostId)) { - logger.error("Unable to complete migration for {}", vm); + logger.error("Migration verification failed for VM {} : VM not found on destination host {} ", vm.getInstanceName(), dstHostId); try { - _agentMgr.send(srcHostId, new Commands(cleanup(vm.getInstanceName())), null); + _agentMgr.send(srcHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null); } catch (final AgentUnavailableException e) { - logger.error("Unable to cleanup source host [{}] due to [{}].", fromHost, e.getMessage(), e); + logger.error("AgentUnavailableException while cleanup on source host: {}", fromHost, e); } cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true); throw new CloudRuntimeException("Unable to complete migration for " + vm); } } catch (final OperationTimedoutException e) { - logger.debug("Error while checking the {} on {}", vm, dstHost, e); + logger.warn("Error while checking the vm {} on host {}", vm, dest.getHost(), e); } - migrated = true; } finally { if (!migrated) { - logger.info("Migration was unsuccessful. Cleaning up: {}", vm); + logger.info("Migration was unsuccessful. Cleaning up: {}", vm); + _networkMgr.rollbackNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), dstHostId); + // deallocate GPU devices for the VM on the destination host + gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), dstHostId); - String alertSubject = String.format("Unable to migrate %s from %s in Zone [%s] and Pod [%s].", - vm.getInstanceName(), fromHost, dest.getDataCenter().getName(), dest.getPod().getName()); - String alertBody = "Migrate Command failed. Please check logs."; - _alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), alertSubject, alertBody); + _alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), + "Unable to migrate vm " + vm.getInstanceName() + " from host " + fromHost.getName() + " in zone " + dest.getDataCenter().getName() + " and pod " + + dest.getPod().getName(), "Migrate Command failed. Please check logs."); try { - _agentMgr.send(dstHostId, new Commands(cleanup(vm.getInstanceName())), null); + _agentMgr.send(dstHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null); } catch (final AgentUnavailableException ae) { - logger.info("Looks like the destination Host is unavailable for cleanup"); + logger.warn("Destination host {} unavailable for cleanup after failed migration of VM {}", dstHostId, vm.getInstanceName(), ae); } _networkMgr.setHypervisorHostname(profile, dest, false); try { stateTransitTo(vm, Event.OperationFailed, srcHostId); } catch (final NoTransitionException e) { - logger.warn(e.getMessage(), e); + logger.warn(e.getMessage()); } } else { + logger.info("Migration completed successfully for VM %s" + vm); + _networkMgr.commitNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), srcHostId); + // deallocate GPU devices for the VM on the src host after migration is complete + gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), srcHostId); _networkMgr.setHypervisorHostname(profile, dest, true); + recreateCheckpointsKvmOnVmAfterMigration(vm, dstHostId); updateVmPod(vm, dstHostId); } @@ -5029,961 +1796,798 @@ private void orchestrateMigrateForScale(final String vmUuid, final long srcHostI } } - @Override - public boolean replugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final Host host) throws ConcurrentOperationException, - ResourceUnavailableException, InsufficientCapacityException { - boolean result = true; + /** + * Create and set parameters for the {@link MigrateCommand} used in the migration and scaling of VMs. + */ + protected MigrateCommand buildMigrateCommand(VMInstanceVO vmInstance, VirtualMachineTO virtualMachineTO, DeployDestination destination, Answer answer, + Map dpdkInterfaceMapping) { + final boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vmInstance.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); + final MigrateCommand migrateCommand = new MigrateCommand(vmInstance.getInstanceName(), destination.getHost().getPrivateIpAddress(), isWindows, virtualMachineTO, + getExecuteInSequence(vmInstance.getHypervisorType())); - final VMInstanceVO router = _vmDao.findById(vm.getId()); - if (router.getState() == State.Running) { - try { - final ReplugNicCommand replugNicCmd = new ReplugNicCommand(nic, vm.getName(), vm.getType(), vm.getDetails()); - final Commands cmds = new Commands(Command.OnError.Stop); - cmds.addCommand("replugnic", replugNicCmd); - _agentMgr.send(host.getId(), cmds); - final ReplugNicAnswer replugNicAnswer = cmds.getAnswer(ReplugNicAnswer.class); - if (replugNicAnswer == null || !replugNicAnswer.getResult()) { - logger.warn("Unable to replug nic for vm {}", vm.getName()); - result = false; - } - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException("Unable to plug nic for router " + vm.getName() + " in network " + network, host.getId(), e); - } - } else { - String message = String.format("Unable to apply ReplugNic, VM [%s] is not in the right state (\"Running\"). VM state [%s].", router.toString(), router.getState()); - logger.warn(message); + Map vlanToPersistenceMap = vmVlanPersistenceMappingService.getVlanToPersistenceMapForVM(vmInstance.getId()); + if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { + logger.debug("Setting VLAN persistence to [{}] as part of migrate command for VM [{}].", new Gson().toJson(vlanToPersistenceMap), virtualMachineTO); + migrateCommand.setVlanToPersistenceMap(vlanToPersistenceMap); + } + + logger.debug("Setting auto convergence to: {}", StorageManager.KvmAutoConvergence.value()); + migrateCommand.setAutoConvergence(StorageManager.KvmAutoConvergence.value()); + migrateCommand.setHostGuid(destination.getHost().getGuid()); + + PrepareForMigrationAnswer prepareForMigrationAnswer = (PrepareForMigrationAnswer) answer; + + Map answerDpdkInterfaceMapping = prepareForMigrationAnswer.getDpdkInterfaceMapping(); + if (MapUtils.isNotEmpty(answerDpdkInterfaceMapping) && dpdkInterfaceMapping != null) { + logger.debug("Setting DPDK interface mapping to [{}] as part of migrate command for VM [{}].", new Gson().toJson(vlanToPersistenceMap), + virtualMachineTO); + dpdkInterfaceMapping.putAll(answerDpdkInterfaceMapping); + migrateCommand.setDpdkInterfaceMapping(dpdkInterfaceMapping); + } - throw new ResourceUnavailableException(message, DataCenter.class, router.getDataCenterId()); + Integer newVmCpuShares = prepareForMigrationAnswer.getNewVmCpuShares(); + if (newVmCpuShares != null) { + logger.debug("Setting CPU shares to [{}] as part of migrate command for VM [{}].", newVmCpuShares, virtualMachineTO); + migrateCommand.setNewVmCpuShares(newVmCpuShares); } - return result; + return migrateCommand; } - public boolean plugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final ReservationContext context, final DeployDestination dest) throws ConcurrentOperationException, - ResourceUnavailableException, InsufficientCapacityException { - boolean result = true; + void updateVmPod(VMInstanceVO vm, long dstHostId) { + // update the VMs pod + HostVO host = _hostDao.findById(dstHostId); + VMInstanceVO newVm = _vmDao.findById(vm.getId()); + newVm.setPodIdToDeployIn(host.getPodId()); + _vmDao.persist(newVm); + } - final VMInstanceVO router = _vmDao.findById(vm.getId()); - if (router.getState() == State.Running) { - try { - NetworkDetailVO pvlanTypeDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.ISOLATED_PVLAN_TYPE); - if (pvlanTypeDetail != null) { - Map nicDetails = nic.getDetails() == null ? new HashMap<>() : nic.getDetails(); - logger.debug("Found PVLAN type: {} on network details, adding it as part of the PlugNicCommand", pvlanTypeDetail.getValue()); - nicDetails.putIfAbsent(NetworkOffering.Detail.pvlanType, pvlanTypeDetail.getValue()); - nic.setDetails(nicDetails); - } - final PlugNicCommand plugNicCmd = new PlugNicCommand(nic, vm.getName(), vm.getType(), vm.getDetails()); - final Commands cmds = new Commands(Command.OnError.Stop); - cmds.addCommand("plugnic", plugNicCmd); - _agentMgr.send(dest.getHost().getId(), cmds); - final PlugNicAnswer plugNicAnswer = cmds.getAnswer(PlugNicAnswer.class); - if (plugNicAnswer == null || !plugNicAnswer.getResult()) { - logger.warn("Unable to plug nic for vm {}", vm.getName()); - result = false; - } - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException("Unable to plug nic for router " + vm.getName() + " in network " + network, dest.getHost().getId(), e); - } - } else { - String message = String.format("Unable to apply PlugNic, VM [%s] is not in the right state (\"Running\"). VM state [%s].", router.toString(), router.getState()); - logger.warn(message); + long getNodeId() { + return _nodeId; + } - throw new ResourceUnavailableException(message, DataCenter.class, - router.getDataCenterId()); - } + protected Map createMappingVolumeAndStoragePool(VirtualMachineProfile profile, Host targetHost, Map userDefinedMapOfVolumesAndStoragePools) { + return vmVolumeMigrationPlanningService.createMappingVolumeAndStoragePool(profile, targetHost, userDefinedMapOfVolumesAndStoragePools); + } - return result; + private Map createMappingVolumeAndStoragePool(final VirtualMachineProfile profile, final DataCenterDeployment plan, final Map userDefinedMapOfVolumesAndStoragePools) { + return vmVolumeMigrationPlanningService.createMappingVolumeAndStoragePool(profile, plan, userDefinedMapOfVolumesAndStoragePools); } - public boolean unplugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final ReservationContext context, final DeployDestination dest) throws ConcurrentOperationException, - ResourceUnavailableException { + protected List findVolumesThatWereNotMappedByTheUser(VirtualMachineProfile profile, Map volumeToStoragePoolObjectMap) { + return vmVolumeMigrationPlanningServiceImpl.findVolumesThatWereNotMappedByTheUser(profile, volumeToStoragePoolObjectMap); + } - boolean result = true; - final VMInstanceVO router = _vmDao.findById(vm.getId()); + protected Map buildMapUsingUserInformation(VirtualMachineProfile profile, Host targetHost, Map userDefinedVolumeToStoragePoolMap) { + return vmVolumeMigrationPlanningServiceImpl.buildMapUsingUserInformation(profile, targetHost, userDefinedVolumeToStoragePoolMap); + } - if (router.getState() == State.Running) { - UserVmVO userVm = _userVmDao.findById(vm.getId()); - if (userVm != null && userVm.getType() == VirtualMachine.Type.User) { - _userVmService.collectVmNetworkStatistics(userVm); - } - try { - final Commands cmds = new Commands(Command.OnError.Stop); - final UnPlugNicCommand unplugNicCmd = new UnPlugNicCommand(nic, vm.getName()); - Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); - if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { - unplugNicCmd.setVlanToPersistenceMap(vlanToPersistenceMap); - } - cmds.addCommand("unplugnic", unplugNicCmd); - _agentMgr.send(dest.getHost().getId(), cmds); + protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) { + vmVolumeMigrationPlanningServiceImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPool, volume, targetPool); + } - final UnPlugNicAnswer unplugNicAnswer = cmds.getAnswer(UnPlugNicAnswer.class); - if (unplugNicAnswer == null || !unplugNicAnswer.getResult()) { - logger.warn("Unable to unplug nic from router {}", router); - result = false; - } - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException("Unable to unplug nic from rotuer " + router + " from network " + network, dest.getHost().getId(), e); - } - } else if (router.getState() == State.Stopped || router.getState() == State.Stopping) { - logger.debug("Vm {} is in {}, so not sending unplug nic command to the backend", router.getInstanceName(), router.getState()); - } else { - String message = String.format("Unable to apply unplug nic, VM [%s] is not in the right state (\"Running\"). VM state [%s].", router.toString(), router.getState()); - logger.warn(message); + protected void createStoragePoolMappingsForVolumes(VirtualMachineProfile profile, DataCenterDeployment plan, Map volumeToPoolObjectMap, List volumesNotMapped) { + vmVolumeMigrationPlanningServiceImpl.createStoragePoolMappingsForVolumes(profile, plan, volumeToPoolObjectMap, volumesNotMapped); + } - throw new ResourceUnavailableException(message, DataCenter.class, router.getDataCenterId()); - } + protected boolean shouldMapVolume(VirtualMachineProfile profile, StoragePoolVO currentPool) { + return vmVolumeMigrationPlanningServiceImpl.shouldMapVolume(profile, currentPool); + } - return result; + protected void executeManagedStorageChecksWhenTargetStoragePoolNotProvided(Host targetHost, StoragePoolVO currentPool, Volume volume) { + vmVolumeMigrationPlanningServiceImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(targetHost, currentPool, volume); } - @Override - public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, - Map customParameters, final boolean reconfiguringOnExistingHost) - throws ResourceUnavailableException, InsufficientServerCapacityException, ConcurrentOperationException { + protected boolean isStorageCrossClusterMigration(Long clusterId, StoragePoolVO currentPool) { + return vmVolumeMigrationPlanningServiceImpl.isStorageCrossClusterMigration(clusterId, currentPool); + } + + protected void createVolumeToStoragePoolMappingIfPossible(VirtualMachineProfile profile, DataCenterDeployment plan, Map volumeToPoolObjectMap, Volume volume, + StoragePoolVO currentPool) { + vmVolumeMigrationPlanningServiceImpl.createVolumeToStoragePoolMappingIfPossible(profile, plan, volumeToPoolObjectMap, volume, currentPool); + } + + protected List getCandidateStoragePoolsToMigrateLocalVolume(VirtualMachineProfile profile, DataCenterDeployment plan, Volume volume) { + return vmVolumeMigrationPlanningServiceImpl.getCandidateStoragePoolsToMigrateLocalVolume(profile, plan, volume); + } + + private void moveVmToMigratingState(final T vm, final Long hostId, final ItWorkVO work) throws ConcurrentOperationException { + try { + if (!changeState(vm, Event.MigrationRequested, hostId, work, Step.Migrating)) { + logger.error("Migration cancelled because state has changed: " + vm); + throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm); + } + } catch (final NoTransitionException e) { + logger.error("Migration cancelled because " + e.getMessage(), e); + throw new ConcurrentOperationException("Migration cancelled because " + e.getMessage()); + } + } + + private void moveVmOutofMigratingStateOnSuccess(final T vm, final Long hostId, final ItWorkVO work) throws ConcurrentOperationException { + try { + if (!changeState(vm, Event.OperationSucceeded, hostId, work, Step.Started)) { + logger.error("Unable to change the state for " + vm); + throw new ConcurrentOperationException("Unable to change the state for " + vm); + } + } catch (final NoTransitionException e) { + logger.error("Unable to change state due to " + e.getMessage(), e); + throw new ConcurrentOperationException("Unable to change state due to " + e.getMessage()); + } + } + + @Override + public void migrateWithStorage(final String vmUuid, final long srcHostId, final long destHostId, final Map volumeToPool) + throws ResourceUnavailableException, ConcurrentOperationException { final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { final VirtualMachine vm = _vmDao.findByUuid(vmUuid); - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); try { - return orchestrateReConfigureVm(vmUuid, oldServiceOffering, newServiceOffering, reconfiguringOnExistingHost); + orchestrateMigrateWithStorage(vmUuid, srcHostId, destHostId, volumeToPool); } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } } else { - final Outcome outcome = reconfigureVmThroughJobQueue(vmUuid, oldServiceOffering, newServiceOffering, customParameters, reconfiguringOnExistingHost); + final Outcome outcome = vmWorkJobQueueService.migrateVmWithStorageThroughJobQueue(vmUuid, srcHostId, destHostId, volumeToPool); - VirtualMachine vm = retrieveVmFromJobOutcome(outcome, vmUuid, "reconfigureVm"); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmWithStorage"); - Object result = null; try { - result = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); - } catch (Exception ex) { - throw new RuntimeException("Unhandled exception", ex); - } - - if (result != null) { - throw new RuntimeException(String.format("Unexpected job execution result [%s]", result)); + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (InsufficientCapacityException ex) { + throw new RuntimeException("Unexpected exception", ex); } - - return (VMInstanceVO)vm; } } - private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, - boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException { + private void orchestrateMigrateWithStorage(final String vmUuid, final long srcHostId, final long destHostId, final Map volumeToPool) throws ResourceUnavailableException, + ConcurrentOperationException { + final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - HostVO hostVo = _hostDao.findById(vm.getHostId()); + final HostVO srcHost = _hostDao.findById(srcHostId); + final HostVO destHost = _hostDao.findById(destHostId); + final VirtualMachineGuru vmGuru = getVmGuru(vm); - Long clustedId = hostVo.getClusterId(); - Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clustedId); - Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clustedId); - boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clustedId); - boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clustedId); + final DataCenterVO dc = _dcDao.findById(destHost.getDataCenterId()); + final HostPodVO pod = _podDao.findById(destHost.getPodId()); + final Cluster cluster = _clusterDao.findById(destHost.getClusterId()); + final DeployDestination destination = new DeployDestination(dc, pod, cluster, destHost); - int minMemory = (int)(newServiceOffering.getRamSize() / (divideMemoryByOverprovisioning ? memoryOvercommitRatio : 1)); - int minSpeed = (int)(newServiceOffering.getSpeed() / (divideCpuByOverprovisioning ? cpuOvercommitRatio : 1)); + final VirtualMachineProfile vmSrc = new VirtualMachineProfileImpl(vm); + vmSrc.setHost(srcHost); + for (final NicProfile nic : _networkMgr.getNicProfiles(vm)) { + vmSrc.addNic(nic); + } - ScaleVmCommand scaleVmCommand = - new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, - newServiceOffering.getSpeed(), minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, newServiceOffering.getLimitCpuUse()); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()), null, null); + profile.setHost(destHost); - scaleVmCommand.getVirtualMachine().setId(vm.getId()); - scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid()); - scaleVmCommand.getVirtualMachine().setType(vm.getType()); + final Map volumeToPoolMap = createMappingVolumeAndStoragePool(profile, destHost, volumeToPool); - Long dstHostId = vm.getHostId(); + if (volumeToPoolMap == null || volumeToPoolMap.isEmpty()) { + throw new InvalidParameterValueException("Migration of the vm " + vm + "from host " + srcHost + " to destination host " + destHost + + " doesn't involve migrating the volumes."); + } - if (vm.getHypervisorType().equals(HypervisorType.VMware)) { - HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType()); - Map details = hvGuru.getClusterSettings(vm.getId()); - scaleVmCommand.getVirtualMachine().setDetails(details); + AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM_MIGRATE; + if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { + alertType = AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; + } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { + alertType = AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; } - ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Running, vm.getType(), vm.getId()); + _networkMgr.prepareNicForMigration(profile, destination); + volumeMgr.prepareForMigration(profile, destination); + final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(vm.getHypervisorType()); + final VirtualMachineTO to = hvGuru.implement(profile); + ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId()); work.setStep(Step.Prepare); work.setResourceType(ItWorkVO.ResourceType.Host); - work.setResourceId(vm.getHostId()); - _workDao.persist(work); + work.setResourceId(destHostId); + work = _workDao.persist(work); + + vm.setLastHostId(srcHostId); + vm.setPodIdToDeployIn(destHost.getPodId()); + moveVmToMigratingState(vm, destHostId, work); + boolean migrated = false; try { - Answer reconfigureAnswer = _agentMgr.send(vm.getHostId(), scaleVmCommand); + Nic defaultNic = _networkModel.getDefaultNic(vm.getId()); - if (reconfigureAnswer == null || !reconfigureAnswer.getResult()) { - logger.error("Unable to scale vm due to {}", (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); - throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); - } + if (defaultNic != null && VirtualMachine.Type.User.equals(vm.getType())) { + UserVmVO userVm = _userVmDao.findById(vm.getId()); + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); + userVm.setDetails(details); - upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + Network network = _networkModel.getNetwork(defaultNic.getNetworkId()); + if (_networkModel.isSharedNetworkWithoutServices(network.getId())) { + final String serviceOffering = _serviceOfferingDao.findByIdIncludingRemoved(vm.getId(), vm.getServiceOfferingId()).getDisplayText(); + boolean isWindows = _guestOSCategoryDao.findById(_guestOSDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); + List vmData = _networkModel.generateVmData(userVm.getUserData(), userVm.getUserDataDetails(), serviceOffering, vm.getDataCenterId(), vm.getInstanceName(), vm.getHostName(), vm.getId(), + vm.getUuid(), defaultNic.getMacAddress(), userVm.getDetail("SSH.PublicKey"), (String) profile.getParameter(VirtualMachineProfile.Param.VmPassword), isWindows, + VirtualMachineManager.getHypervisorHostname(destination.getHost() != null ? destination.getHost().getName() : "")); + String vmName = vm.getInstanceName(); + String configDriveIsoRootFolder = "/tmp"; + String isoFile = configDriveIsoRootFolder + "/" + vmName + "/configDrive/" + vmName + ".iso"; + profile.setVmData(vmData); + profile.setConfigDriveLabel(VmConfigDriveLabel.value()); + profile.setConfigDriveIsoRootFolder(configDriveIsoRootFolder); + profile.setConfigDriveIsoFile(isoFile); - if (vm.getType().equals(VirtualMachine.Type.User)) { - _userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE); - } + AttachOrDettachConfigDriveCommand dettachCommand = new AttachOrDettachConfigDriveCommand(vm.getInstanceName(), vmData, VmConfigDriveLabel.value(), false); + try { + _agentMgr.send(srcHost.getId(), dettachCommand); + logger.debug("Deleted config drive ISO for vm {} in host {}", vm.getInstanceName(), srcHost); + } catch (OperationTimedoutException e) { + logger.error("TIme out occurred while exeuting command AttachOrDettachConfigDrive {}", e.getMessage(), e); - if (reconfiguringOnExistingHost) { - vm.setServiceOfferingId(oldServiceOffering.getId()); - _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); - vm.setServiceOfferingId(newServiceOffering.getId()); - _capacityMgr.allocateVmCapacity(vm, false); + } + } } - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException("Operation timed out on reconfiguring " + vm, dstHostId); - } catch (final AgentUnavailableException e) { - throw e; - } - - return vm; - - } + volumeMgr.migrateVolumes(vm, to, srcHost, destHost, volumeToPoolMap); - private void removeCustomOfferingDetails(long vmId) { - Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); - details.remove(UsageEventVO.DynamicParameters.cpuNumber.name()); - details.remove(UsageEventVO.DynamicParameters.cpuSpeed.name()); - details.remove(UsageEventVO.DynamicParameters.memory.name()); - List detailList = new ArrayList<>(); - for(Map.Entry entry: details.entrySet()) { - VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); - detailList.add(detailVO); - } - vmInstanceDetailsDao.saveDetails(detailList); - } + moveVmOutofMigratingStateOnSuccess(vm, destHost.getId(), work); - private void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering) { - Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); + try { + if (!checkVmOnHost(vm, destHostId)) { + logger.error("Vm not found on destination host. Unable to complete migration for {}", vm); + try { + _agentMgr.send(srcHostId, new Commands(cleanup(vm.getInstanceName())), null); + } catch (final AgentUnavailableException e) { + logger.error("AgentUnavailableException while cleanup on source host: {}", srcHost, e); + } + cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true); + throw new CloudRuntimeException("VM not found on destination host. Unable to complete migration for " + vm); + } + } catch (final OperationTimedoutException e) { + logger.error("Error while checking the vm {} is on host {}", vm, destHost, e); + } + migrated = true; + } finally { + if (!migrated) { + logger.info("Migration was unsuccessful. Cleaning up: {}", vm); + _networkMgr.rollbackNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), destHostId); - // We need to restore only the customizable parameters. If we save a parameter that is not customizable and attempt - // to restore a VM snapshot, com.cloud.vm.UserVmManagerImpl.validateCustomParameters will fail. - ServiceOffering unfilledOffering = _serviceOfferingDao.findByIdIncludingRemoved(serviceOffering.getId()); - if (unfilledOffering.getCpu() == null) { - details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), serviceOffering.getCpu().toString()); - } - if (unfilledOffering.getSpeed() == null) { - details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), serviceOffering.getSpeed().toString()); - } - if (unfilledOffering.getRamSize() == null) { - details.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString()); - } + _alertMgr.sendAlert(alertType, srcHost.getDataCenterId(), srcHost.getPodId(), + "Unable to migrate vm " + vm.getInstanceName() + " from host " + srcHost.getName() + " in zone " + dc.getName() + " and pod " + dc.getName(), + "Migrate Command failed. Please check logs."); + try { + _agentMgr.send(destHostId, new Commands(cleanup(vm.getInstanceName())), null); + vm.setPodIdToDeployIn(srcHost.getPodId()); + stateTransitTo(vm, Event.OperationFailed, srcHostId); + } catch (final AgentUnavailableException e) { + logger.warn("Looks like the destination Host is unavailable for cleanup.", e); + } catch (final NoTransitionException e) { + logger.error("Error while transitioning vm from migrating to running state.", e); + } + _networkMgr.setHypervisorHostname(profile, destination, false); + } else { + _networkMgr.commitNicForMigration(vmSrc, profile); + volumeMgr.release(vm.getId(), srcHostId); + _networkMgr.setHypervisorHostname(profile, destination, true); + endSnapshotChainForVolumes(volumeToPoolMap, vm.getHypervisorType()); + } - List detailList = new ArrayList<>(); - for (Map.Entry entry: details.entrySet()) { - VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); - detailList.add(detailVO); + work.setStep(Step.Done); + _workDao.update(work.getId(), work); } - vmInstanceDetailsDao.saveDetails(detailList); } - @Override - public String getConfigComponentName() { - return VirtualMachineManager.class.getSimpleName(); + protected void endSnapshotChainForVolumes(Map volumeToPoolMap, HypervisorType hypervisorType) { + vmMigrationCheckpointService.endSnapshotChainForVolumes(volumeToPoolMap, hypervisorType); } - @Override - public ConfigKey[] getConfigKeys() { - return new ConfigKey[] { ClusterDeltaSyncInterval, StartRetry, VmDestroyForcestop, VmOpCancelInterval, VmOpCleanupInterval, VmOpCleanupWait, - VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, - VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, - HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, - AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName, - VmSyncPowerStateTransitioning, SystemVmEnableUserData - }; + protected void recreateCheckpointsKvmOnVmAfterMigration(VMInstanceVO vm, long hostId) { + vmMigrationCheckpointService.recreateCheckpointsKvmOnVmAfterMigration(vm, hostId); } - public List getStoragePoolAllocators() { - return _storagePoolAllocators; + + protected List getVmVolumesWithCheckpointsToRecreate(VMInstanceVO vm) { + return vmMigrationCheckpointService.getVmVolumesWithCheckpointsToRecreate(vm); } - @Inject - public void setStoragePoolAllocators(final List storagePoolAllocators) { - _storagePoolAllocators = storagePoolAllocators; + + @Override + public VirtualMachineTO toVmTO(final VirtualMachineProfile profile) { + final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(profile.getVirtualMachine().getHypervisorType()); + final VirtualMachineTO to = hvGuru.implement(profile); + return to; } - /** - * PowerState report handling for out-of-band changes and handling of left-over transitional VM states - */ + protected void cancelWorkItems(final long nodeId) { + final GlobalLock scanLock = GlobalLock.getInternLock("vmmgr.cancel.workitem"); - @MessageHandler(topic = Topics.VM_POWER_STATE) - protected void HandlePowerStateReport(final String subject, final String senderAddress, final Object args) { - assert args != null; - final Long vmId = (Long)args; - - final List pendingWorkJobs = _workJobDao.listPendingWorkJobs( - VirtualMachine.Type.Instance, vmId); - if (CollectionUtils.isEmpty(pendingWorkJobs) && !_haMgr.hasPendingHaWork(vmId)) { - final VMInstanceVO vm = _vmDao.findById(vmId); - if (vm != null) { - switch (vm.getPowerState()) { - case PowerOn: - handlePowerOnReportWithNoPendingJobsOnVM(vm); - break; - - case PowerOff: - case PowerReportMissing: - handlePowerOffReportWithNoPendingJobsOnVM(vm); - break; - case PowerUnknown: - default: - assert false; - break; + try { + if (scanLock.lock(3)) { + try { + final List works = _workDao.listWorkInProgressFor(nodeId); + for (final ItWorkVO work : works) { + logger.info("Handling unfinished work item: {}", work); + try { + final VMInstanceVO vm = _vmDao.findById(work.getInstanceId()); + if (vm != null) { + if (work.getType() == State.Starting) { + _haMgr.scheduleRestart(vm, true); + work.setManagementServerId(_nodeId); + work.setStep(Step.Done); + _workDao.update(work.getId(), work); + } else if (work.getType() == State.Stopping) { + _haMgr.scheduleStop(vm, vm.getHostId(), WorkType.CheckStop); + work.setManagementServerId(_nodeId); + work.setStep(Step.Done); + _workDao.update(work.getId(), work); + } else if (work.getType() == State.Migrating) { + _haMgr.scheduleMigration(vm); + work.setStep(Step.Done); + _workDao.update(work.getId(), work); + } + } + } catch (final Exception e) { + logger.error("Error while handling {}", work, e); + } + } + } finally { + scanLock.unlock(); } - } else { - logger.warn("VM {} no longer exists when processing VM state report.", vmId); } - } else { - logger.info("There is pending job or HA tasks working on the VM. vm: {}, postpone power-change report by resetting power-change counters.", () -> _vmDao.findById(vmId)); - _vmDao.resetVmPowerStateTracking(vmId); + } finally { + scanLock.releaseRef(); } } - private ApiCommandResourceType getApiCommandResourceTypeForVm(VirtualMachine vm) { - switch (vm.getType()) { - case DomainRouter: - return ApiCommandResourceType.DomainRouter; - case ConsoleProxy: - return ApiCommandResourceType.ConsoleProxy; - case SecondaryStorageVm: - return ApiCommandResourceType.SystemVm; - } - return ApiCommandResourceType.VirtualMachine; + @Override + public void migrateAway(final String vmUuid, final long srcHostId) throws InsufficientServerCapacityException { + vmMigrateAwayPlanningService.migrateAway(vmUuid, srcHostId); } - private void handlePowerOnReportWithNoPendingJobsOnVM(final VMInstanceVO vm) { - Host host = _hostDao.findById(vm.getHostId()); - Host poweredHost = _hostDao.findById(vm.getPowerHostId()); - - switch (vm.getState()) { - case Starting: - logger.info("VM {} is at {} and we received a power-on report while there is no pending jobs on it.", vm.getInstanceName(), vm.getState()); - - try { - stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); - } catch (final NoTransitionException e) { - logger.warn("Unexpected VM state transition exception, race-condition?", e); - } - - logger.info("VM {} is sync-ed to at Running state according to power-on report from hypervisor.", vm.getInstanceName()); - - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), - VM_SYNC_ALERT_SUBJECT, "VM " + vm.getHostName() + "(" + vm.getInstanceName() - + ") state is sync-ed (Starting -> Running) from out-of-context transition. VM network environment may need to be reset"); - break; - - case Running: - try { - if (vm.getHostId() != null && !vm.getHostId().equals(vm.getPowerHostId())) { - logger.info("Detected out of band VM migration from host {} to host {}", () -> _hostDao.findById(vm.getHostId()), () -> _hostDao.findById(vm.getPowerHostId())); - } - stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); - } catch (final NoTransitionException e) { - logger.warn("Unexpected VM state transition exception, race-condition?", e); - } - - break; - - case Stopping: - case Stopped: - logger.info("VM {} is at {} and we received a power-on report while there is no pending jobs on it.", vm.getInstanceName(), vm.getState()); - - try { - stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); - } catch (final NoTransitionException e) { - logger.warn("Unexpected VM state transition exception, race-condition?", e); - } - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), - VM_SYNC_ALERT_SUBJECT, "VM " + vm.getHostName() + "(" + vm.getInstanceName() + ") state is sync-ed (" + vm.getState() - + " -> Running) from out-of-context transition. VM network environment may need to be reset"); - - ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, vm.getDomainId(), - EventTypes.EVENT_VM_START, "Out of band VM power on", vm.getId(), getApiCommandResourceTypeForVm(vm).toString()); - logger.info("VM {} is sync-ed to at Running state according to power-on report from hypervisor.", vm.getInstanceName()); - break; - - case Destroyed: - case Expunging: - logger.info("Receive power on report when Instance is in destroyed or expunging state. Instance: {}, state: {}.", vm, vm.getState()); - break; - - case Migrating: - logger.info("Instance {} is at {} and we received a power-on report while there is no pending jobs on it.", vm, vm.getState()); - try { - stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); - } catch (final NoTransitionException e) { - logger.warn("Unexpected Instance state transition exception, race-condition?", e); - } - logger.info("Instance {} is sync-ed to at Running state according to power-on report from hypervisor.", vm); - break; - - case Error: - default: - logger.info("Receive power on report when Instance is in error or unexpected state. Instance: {}, state: {}.", vm, vm.getState()); - break; - } + private void orchestrateMigrateAway(final String vmUuid, final long srcHostId, final DeploymentPlanner planner) throws InsufficientServerCapacityException { + vmMigrateAwayPlanningService.orchestrateMigrateAway(vmUuid, srcHostId, planner); } - private void handlePowerOffReportWithNoPendingJobsOnVM(final VMInstanceVO vm) { - switch (vm.getState()) { - case Starting: - case Stopping: - case Running: - case Stopped: - ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM,vm.getDomainId(), - EventTypes.EVENT_VM_STOP, "Out of band VM power off", vm.getId(), getApiCommandResourceTypeForVm(vm).toString()); - case Migrating: - logger.info("VM {} is at {} and we received a {} report while there is no pending jobs on it" - , vm, vm.getState(), vm.getPowerState()); - if((HighAvailabilityManager.ForceHA.value() || vm.isHaEnabled()) && vm.getState() == State.Running - && HaVmRestartHostUp.value() - && vm.getHypervisorType() != HypervisorType.VMware - && vm.getHypervisorType() != HypervisorType.Hyperv) { - logger.info("Detected out-of-band stop of a HA enabled VM {}, will schedule restart.", vm); - if (!_haMgr.hasPendingHaWork(vm.getId())) { - _haMgr.scheduleRestart(vm, true); - } else { - logger.info("VM {} already has a pending HA task working on it.", vm); - } - return; - } + /** + * Check if the virtual machine has any volume in cluster-wide pool + * @param vmId id of the virtual machine + * @return true if volume exists on cluster-wide pool else false + */ + @Override + public boolean checkIfVmHasClusterWideVolumes(Long vmId) { + return vmMigrateAwayPlanningService.checkIfVmHasClusterWideVolumes(vmId); + } - if (PowerState.PowerOff.equals(vm.getPowerState())) { - final VirtualMachineGuru vmGuru = getVmGuru(vm); - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - if (!sendStop(vmGuru, profile, true, true)) { - return; - } else { - // Release resources on StopCommand success - releaseVmResources(profile, true); - } - } else if (PowerState.PowerReportMissing.equals(vm.getPowerState())) { - final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - // VM will be sync-ed to Stopped state, release the resources - releaseVmResources(profile, true); - } + @Override + public DataCenterDeployment getMigrationDeployment(final VirtualMachine vm, final Host host, final Long poolId, final ExcludeList excludes) { + return vmMigrateAwayPlanningService.getMigrationDeployment(vm, host, poolId, excludes); + } + protected class CleanupTask extends ManagedContextRunnable { + @Override + protected void runInContext() { + logger.debug("VM Operation Thread Running"); try { - stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOffReport, null); - } catch (final NoTransitionException e) { - logger.warn("Unexpected VM state transition exception, race-condition?", e); + _workDao.cleanup(VmOpCleanupWait.value()); + final Date cutDate = new Date(DateUtil.currentGMTTime().getTime() - VmOpCleanupInterval.value() * 1000); + _workJobDao.expungeCompletedWorkJobs(cutDate); + } catch (final Exception e) { + logger.error("VM Operations failed due to ", e); } + } + } - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), - VM_SYNC_ALERT_SUBJECT, String.format("VM %s(%s) state is sync-ed (%s -> Stopped) from out-of-context transition.", - vm.getHostName(), vm, vm.getState())); - - logger.info("VM {} is sync-ed to at Stopped state according to power-off report from hypervisor.", vm); - - break; - - case Destroyed: - case Expunging: - break; + @Override + public void reboot(final String vmUuid, final Map params) throws InsufficientCapacityException, ResourceUnavailableException { + vmRebootOrchestrationService.reboot(vmUuid, params); + } - case Error: - default: - break; - } + @Override + public void advanceReboot(final String vmUuid, final Map params) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + vmRebootOrchestrationService.advanceReboot(vmUuid, params); } /** - * Scans stalled VMs in transition states on an UP host and processes them accordingly. - * - *

This method is executed only when the {@code syncTransitioningVmPowerState} flag is enabled. It identifies - * VMs stuck in specific states (e.g., Starting, Stopping, Migrating) on a host that is UP, except for those - * in the Expunging state, which require special handling.

- * - *

The following conditions are checked during the scan: - *

    - *
  • No pending {@code VmWork} job exists for the VM.
  • - *
  • The VM is associated with the given {@code hostId}, and the host is UP.
  • - *
- *

- * - *

When a host is UP, a state report for the VMs will typically be received. However, certain scenarios - * (e.g., out-of-band changes or behavior specific to hypervisors like XenServer or KVM) might result in - * missing reports, preventing the state-sync logic from running. To address this, the method scans VMs - * based on their last update timestamp. If a VM remains stalled without a status update while its host is UP, - * it is assumed to be powered off, which is generally a safe assumption.

- * - * @param hostId the ID of the host to scan for stalled VMs in transition states. + * This method helps constructing vmSpec for Unmanage operation for Stopped Instance + * @param vmId + * @param hostId + * @return VirtualMachineTO */ - private void scanStalledVMInTransitionStateOnUpHost(final long hostId) { - if (!syncTransitioningVmPowerState) { - return; - } - if (!_hostDao.isHostUp(hostId)) { - return; - } - final long stallThresholdInMs = VmJobStateReportInterval.value() * 2; - final long cutTime = new Date(DateUtil.currentGMTTime().getTime() - stallThresholdInMs).getTime(); - final List hostTransitionVms = _vmDao.listByHostAndState(hostId, State.Starting, State.Stopping, State.Migrating); + protected VirtualMachineTO prepVmSpecForUnmanageCmd(Long vmId, Long hostId) { + final VMInstanceVO vm = _vmDao.findById(vmId); + final Account owner = _entityMgr.findById(Account.class, vm.getAccountId()); + final ServiceOfferingVO offering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); + final VirtualMachineTemplate template = _entityMgr.findByIdIncludingRemoved(VirtualMachineTemplate.class, vm.getTemplateId()); + Host host = _hostDao.findById(hostId); + VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, template, offering, owner, null); + updateOverCommitRatioForVmProfile(vmProfile, host.getClusterId()); + final List nics = _nicsDao.listByVmId(vmProfile.getId()); + Collections.sort(nics, (nic1, nic2) -> { + Long nicId1 = Long.valueOf(nic1.getDeviceId()); + Long nicId2 = Long.valueOf(nic2.getDeviceId()); + return nicId1.compareTo(nicId2); + }); - final List mostLikelyStoppedVMs = listStalledVMInTransitionStateOnUpHost(hostTransitionVms, cutTime); - for (final VMInstanceVO vm : mostLikelyStoppedVMs) { - handlePowerOffReportWithNoPendingJobsOnVM(vm); + for (final NicVO nic : nics) { + final Network network = _networkModel.getNetwork(nic.getNetworkId()); + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), + _networkModel.getNetworkTag(vmProfile.getHypervisorType(), network)); + vmProfile.addNic(nicProfile); } - final List vmsWithRecentReport = listVMInTransitionStateWithRecentReportOnUpHost(hostTransitionVms, cutTime); - for (final VMInstanceVO vm : vmsWithRecentReport) { - if (vm.getPowerState() == PowerState.PowerOn) { - handlePowerOnReportWithNoPendingJobsOnVM(vm); - } else { - handlePowerOffReportWithNoPendingJobsOnVM(vm); - } + List volumes = _volsDao.findUsableVolumesForInstance(vmId); + for (VolumeVO vol: volumes) { + VolumeInfo volumeInfo = volumeDataFactory.getVolume(vol.getId()); + DataTO dataTO = volumeInfo.getTO(); + DiskTO disk = storageMgr.getDiskWithThrottling(dataTO, vol.getVolumeType(), vol.getDeviceId(), vol.getPath(), vm.getServiceOfferingId(), vol.getDiskOfferingId()); + vmProfile.addDisk(disk); } - } + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId, + List.of(VirtualMachineProfile.Param.BootType.getName(), VirtualMachineProfile.Param.BootMode.getName(), + VirtualMachineProfile.Param.UefiFlag.getName())); - private void scanStalledVMInTransitionStateOnDisconnectedHosts() { - final Date cutTime = new Date(DateUtil.currentGMTTime().getTime() - VmOpWaitInterval.value() * 1000); - final List stuckAndUncontrollableVMs = listStalledVMInTransitionStateOnDisconnectedHosts(cutTime); - for (final Long vmId : stuckAndUncontrollableVMs) { - final VMInstanceVO vm = _vmDao.findById(vmId); - - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), - VM_SYNC_ALERT_SUBJECT, String.format("VM %s(%s) is stuck in %s state and its host is unreachable for too long", - vm.getHostName(), vm, vm.getState())); + if (details.containsKey(VirtualMachineProfile.Param.BootType.getName())) { + vmProfile.getParameters().put(VirtualMachineProfile.Param.BootType, details.get(VirtualMachineProfile.Param.BootType.getName())); } - } - private List listStalledVMInTransitionStateOnUpHost( - final List transitioningVms, final long cutTime) { - if (CollectionUtils.isEmpty(transitioningVms)) { - return transitioningVms; + if (details.containsKey(VirtualMachineProfile.Param.BootMode.getName())) { + vmProfile.getParameters().put(VirtualMachineProfile.Param.BootMode, details.get(VirtualMachineProfile.Param.BootMode.getName())); } - List vmIdsInProgress = vmIdsInProgressCache.get(); - return transitioningVms.stream() - .filter(v -> v.getPowerStateUpdateTime().getTime() < cutTime && !vmIdsInProgress.contains(v.getId())) - .collect(Collectors.toList()); - } - private List listVMInTransitionStateWithRecentReportOnUpHost( - final List transitioningVms, final long cutTime) { - if (CollectionUtils.isEmpty(transitioningVms)) { - return transitioningVms; + if (details.containsKey(VirtualMachineProfile.Param.UefiFlag.getName())) { + vmProfile.getParameters().put(VirtualMachineProfile.Param.UefiFlag, details.get(VirtualMachineProfile.Param.UefiFlag.getName())); } - List vmIdsInProgress = vmIdsInProgressCache.get(); - return transitioningVms.stream() - .filter(v -> v.getPowerStateUpdateTime().getTime() > cutTime && !vmIdsInProgress.contains(v.getId())) - .collect(Collectors.toList()); - } - - private List listStalledVMInTransitionStateOnDisconnectedHosts(final Date cutTime) { - final String sql = "SELECT i.* " + - "FROM vm_instance AS i " + - "INNER JOIN host AS h ON i.host_id = h.id " + - "WHERE h.status != 'UP' " + - " AND i.power_state_update_time < ? " + - " AND i.state IN ('Starting', 'Stopping', 'Migrating') " + - " AND i.id NOT IN (SELECT vm_instance_id FROM vm_work_job AS w " + - " INNER JOIN async_job AS j ON w.id = j.id " + - " WHERE j.job_status = ?) " + - " AND i.removed IS NULL"; - - final List l = new ArrayList<>(); - TransactionLegacy txn = TransactionLegacy.currentTxn(); - String cutTimeStr = DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), cutTime); - int jobStatusInProgress = JobInfo.Status.IN_PROGRESS.ordinal(); - try { - PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql); + return toVmTO(vmProfile); + } - pstmt.setString(1, cutTimeStr); - pstmt.setInt(2, jobStatusInProgress); - final ResultSet rs = pstmt.executeQuery(); - while (rs.next()) { - l.add(rs.getLong(1)); + protected VirtualMachineTO getVmTO(Long vmId) { + final VMInstanceVO vm = _vmDao.findById(vmId); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + final List nics = _nicsDao.listByVmId(profile.getId()); + Collections.sort(nics, new Comparator() { + @Override + public int compare(NicVO nic1, NicVO nic2) { + Long nicId1 = Long.valueOf(nic1.getDeviceId()); + Long nicId2 = Long.valueOf(nic2.getDeviceId()); + return nicId1.compareTo(nicId2); } - } catch (final SQLException e) { - logger.error("Unable to execute SQL [{}] with params {\"i.power_state_update_time\": \"{}\", \"j.job_status\": {}} due to [{}].", sql, cutTimeStr, jobStatusInProgress, e.getMessage(), e); + }); + + for (final NicVO nic : nics) { + final Network network = _networkModel.getNetwork(nic.getNetworkId()); + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), + _networkModel.getNetworkTag(profile.getHypervisorType(), network)); + profile.addNic(nicProfile); } - return l; + final VirtualMachineTO to = toVmTO(profile); + return to; } - public class VmStateSyncOutcome extends OutcomeImpl { - private long _vmId; + public Command cleanup(final VirtualMachine vm, Map dpdkInterfaceMapping) { + return vmStopCommandService.buildCleanupCommand(vm, getExecuteInSequence(vm.getHypervisorType()), dpdkInterfaceMapping); + } - public VmStateSyncOutcome(final AsyncJob job, final PowerState desiredPowerState, final long vmId, final Long srcHostIdForMigration) { - super(VirtualMachine.class, job, VmJobCheckInterval.value(), new Predicate() { - @Override - public boolean checkCondition() { - final AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId()); - return jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS; - } - }, Topics.VM_POWER_STATE, AsyncJob.Topics.JOB_STATE); - _vmId = vmId; - } + public Command cleanup(final String vmName) { + return vmStopCommandService.buildCleanupCommand(vmName, getExecuteInSequence(null)); + } - @Override - protected VirtualMachine retrieve() { - return _vmDao.findById(_vmId); - } + public void syncVMMetaData(final Map vmMetadatum) { + vmMetadataSyncService.syncVMMetaData(vmMetadatum); } - public class VmJobVirtualMachineOutcome extends OutcomeImpl { - private long _vmId; + @Override + public boolean isRecurring() { + return true; + } - public VmJobVirtualMachineOutcome(final AsyncJob job, final long vmId) { - super(VirtualMachine.class, job, VmJobCheckInterval.value(), new Predicate() { - @Override - public boolean checkCondition() { - final AsyncJobVO jobVo = _entityMgr.findById(AsyncJobVO.class, job.getId()); - return jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS; + @Override + public boolean processAnswers(final long agentId, final long seq, final Answer[] answers) { + for (final Answer answer : answers) { + if ( answer instanceof ClusterVMMetaDataSyncAnswer) { + final ClusterVMMetaDataSyncAnswer cvms = (ClusterVMMetaDataSyncAnswer)answer; + if (!cvms.isExecuted()) { + syncVMMetaData(cvms.getVMMetaDatum()); + cvms.setExecuted(); } - }, AsyncJob.Topics.JOB_STATE); - _vmId = vmId; - } - - @Override - protected VirtualMachine retrieve() { - return _vmDao.findById(_vmId); + } } + return true; } - public Outcome startVmThroughJobQueue(final String vmUuid, - final Map params, - final DeploymentPlan planToDeploy, final DeploymentPlanner planner) { - String commandName = VmWorkStart.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, VmWorkJobVO.Step.Starting, vmId); + @Override + public boolean processTimeout(final long agentId, final long seq) { + return true; + } - workJob = newVmWorkJobAndInfo.first(); - VmWorkStart workInfo = new VmWorkStart(newVmWorkJobAndInfo.second()); + @Override + public int getTimeout() { + return -1; + } - workInfo.setPlan(planToDeploy); - workInfo.setParams(params); - if (planner != null) { - workInfo.setDeploymentPlanner(planner.getName()); + @Override + public boolean processCommands(final long agentId, final long seq, final Command[] cmds) { + boolean processed = false; + for (final Command cmd : cmds) { + if (cmd instanceof PingRoutingCommand) { + final PingRoutingCommand ping = (PingRoutingCommand)cmd; + if (ping.getHostVmStateReport() != null) { + _syncMgr.processHostVmStatePingReport(agentId, ping.getHostVmStateReport(), ping.getOutOfBand()); + } + vmPowerStateSyncManager.scanStalledVMInTransitionStateOnUpHost(agentId); + processed = true; } - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); } - - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmStateSyncOutcome(workJob, - VirtualMachine.PowerState.PowerOn, vmId, null); + return processed; } - public Outcome stopVmThroughJobQueue(final String vmUuid, final boolean cleanup) { - String commandName = VmWorkStop.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(null, vmUuid, null, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, VmWorkJobVO.Step.Prepare, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkStop workInfo = new VmWorkStop(newVmWorkJobAndInfo.second(), cleanup); - - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmStateSyncOutcome(workJob, - VirtualMachine.PowerState.PowerOff, vmId, null); + @Override + public AgentControlAnswer processControlCommand(final long agentId, final AgentControlCommand cmd) { + return null; } - public Outcome rebootVmThroughJobQueue(final String vmUuid, - final Map params) { - String commandName = VmWorkReboot.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, VmWorkJobVO.Step.Prepare, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkReboot workInfo = new VmWorkReboot(newVmWorkJobAndInfo.second(), params); + @Override + public boolean processDisconnect(final long agentId, final Status state) { + return true; + } - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } + @Override + public void processHostAboutToBeRemoved(long hostId) { + } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + @Override + public void processHostRemoved(long hostId, long clusterId) { + } - return new VmJobVirtualMachineOutcome(workJob, - vmId); + @Override + public void processHostAdded(long hostId) { } - public Outcome migrateVmThroughJobQueue(final String vmUuid, final long srcHostId, final DeployDestination dest) { - Map volumeStorageMap = dest.getStorageForDisks(); - if (volumeStorageMap != null) { - for (Volume vol : volumeStorageMap.keySet()) { - checkConcurrentJobsPerDatastoreThreshhold(volumeStorageMap.get(vol)); - } + @Override + public void processConnect(final Host agent, final StartupCommand cmd, final boolean forRebalance) throws ConnectionException { + if (!(cmd instanceof StartupRoutingCommand)) { + return; } - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - Long vmId = vm.getId(); - - String commandName = VmWorkMigrate.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, vmUuid, VirtualMachine.Type.Instance, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + logger.debug("Received startup command from hypervisor host. host: {}", agent); - workJob = newVmWorkJobAndInfo.first(); - VmWorkMigrate workInfo = new VmWorkMigrate(newVmWorkJobAndInfo.second(), srcHostId, dest); + _syncMgr.resetHostSyncState(agent); - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + if (forRebalance) { + logger.debug("Not processing listener {} as connect happens on rebalance process", this); + return; } + final Long clusterId = agent.getClusterId(); + final long agentId = agent.getId(); - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmStateSyncOutcome(workJob, - VirtualMachine.PowerState.PowerOn, vmId, vm.getPowerHostId()); - } - - public Outcome migrateVmAwayThroughJobQueue(final String vmUuid, final long srcHostId) { - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - Long vmId = vm.getId(); - - String commandName = VmWorkMigrateAway.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, vmUuid, VirtualMachine.Type.Instance, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkMigrateAway workInfo = new VmWorkMigrateAway(newVmWorkJobAndInfo.second(), srcHostId); - - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + if (agent.getHypervisorType() == HypervisorType.XenServer) { + final ClusterVMMetaDataSyncCommand syncVMMetaDataCmd = new ClusterVMMetaDataSyncCommand(ClusterVMMetaDataSyncInterval.value(), clusterId); + try { + final long seq_no = _agentMgr.send(agentId, new Commands(syncVMMetaDataCmd), this); + logger.debug("Cluster VM metadata sync started with jobid {}", seq_no); + } catch (final AgentUnavailableException e) { + logger.fatal("The Cluster VM metadata sync process failed for cluster {} with {}", _clusterDao.findById(clusterId), e); + } } - - - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmStateSyncOutcome(workJob, VirtualMachine.PowerState.PowerOn, vmId, vm.getPowerHostId()); } - public Outcome migrateVmWithStorageThroughJobQueue( - final String vmUuid, final long srcHostId, final long destHostId, - final Map volumeToPool) { - String commandName = VmWorkMigrateWithStorage.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + protected class TransitionTask extends ManagedContextRunnable { + @Override + protected void runInContext() { + final GlobalLock lock = GlobalLock.getInternLock("TransitionChecking"); + if (lock == null) { + logger.debug("Couldn't get the global lock"); + return; + } - workJob = newVmWorkJobAndInfo.first(); - VmWorkMigrateWithStorage workInfo = new VmWorkMigrateWithStorage(newVmWorkJobAndInfo.second(), srcHostId, destHostId, volumeToPool); + if (!lock.lock(30)) { + logger.debug("Couldn't lock the db"); + return; + } + try { + vmPowerStateSyncManager.scanStalledVMInTransitionStateOnDisconnectedHosts(); - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + final List instances = _vmDao.findVMInTransition(new Date(DateUtil.currentGMTTime().getTime() - AgentManager.Wait.value() * 1000), State.Starting, State.Stopping); + for (final VMInstanceVO instance : instances) { + final State state = instance.getState(); + if (state == State.Stopping) { + _haMgr.scheduleStop(instance, instance.getHostId(), WorkType.CheckStop); + } else if (state == State.Starting) { + _haMgr.scheduleRestart(instance, true); + } + } + } catch (final Exception e) { + logger.warn("Caught the following exception on transition checking", e); + } finally { + lock.unlock(); + } } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmStateSyncOutcome(workJob, - VirtualMachine.PowerState.PowerOn, vmId, destHostId); } - public Outcome migrateVmForScaleThroughJobQueue( - final String vmUuid, final long srcHostId, final DeployDestination dest, final Long newSvcOfferingId) { - String commandName = VmWorkMigrateForScale.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkMigrateForScale workInfo = new VmWorkMigrateForScale(newVmWorkJobAndInfo.second(), srcHostId, dest, newSvcOfferingId); - - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmJobVirtualMachineOutcome(workJob, vmId); + @Override + public VMInstanceVO findById(final long vmId) { + return _vmDao.findById(vmId); } - private void checkConcurrentJobsPerDatastoreThreshhold(final StoragePool destPool) { - final Long threshold = VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value(); - if (threshold != null && threshold > 0) { - long count = _jobMgr.countPendingJobs("\"storageid\":\"" + destPool.getUuid() + "\"", MigrateVMCmd.class.getName(), MigrateVolumeCmd.class.getName(), MigrateVolumeCmdByAdmin.class.getName()); - if (count > threshold) { - throw new CloudRuntimeException("Number of concurrent migration jobs per datastore exceeded the threshold: " + threshold.toString() + ". Please try again after some time."); - } - } + @Override + public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffering newServiceOffering) { + vmServiceOfferingUpgradeManager.checkIfCanUpgrade(vmInstance, newServiceOffering); } - public Outcome migrateVmStorageThroughJobQueue(final String vmUuid, final Map volumeToPool) { - Collection poolIds = volumeToPool.values(); - Set uniquePoolIds = new HashSet<>(poolIds); - for (Long poolId : uniquePoolIds) { - StoragePoolVO pool = _storagePoolDao.findById(poolId); - checkConcurrentJobsPerDatastoreThreshhold(pool); - } - - String commandName = VmWorkStorageMigration.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkStorageMigration workInfo = new VmWorkStorageMigration(newVmWorkJobAndInfo.second(), volumeToPool); + /** + * Throws an InvalidParameterValueException in case the new service offerings does not match the storage scope (e.g. local or shared). + */ + protected void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, DiskOffering newDiskOffering) { + vmServiceOfferingUpgradeManager.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newDiskOffering); + } - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + public boolean isRootVolumeOnLocalStorage(long vmId) { + return vmServiceOfferingUpgradeManager.isRootVolumeOnLocalStorage(vmId); + } - return new VmJobVirtualMachineOutcome(workJob, vmId); + @Override + public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, ServiceOffering currentServiceOffering) { + return vmServiceOfferingUpgradeManager.upgradeVmDb(vmId, newServiceOffering, currentServiceOffering); } - public Outcome addVmToNetworkThroughJobQueue( - final VirtualMachine vm, final Network network, final NicProfile requested) { - Long vmId = vm.getId(); - String commandName = VmWorkAddVmToNetwork.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + @Override + public NicProfile addVmToNetwork(final VirtualMachine vm, final Network network, final NicProfile requested) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - final CallContext context = CallContext.current(); - final User user = context.getCallingUser(); - final Account account = context.getCallingAccount(); + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId(), network.getUuid()); + try { + return orchestrateAddVmToNetwork(vm, network, requested); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); + } + } else { + final Outcome outcome = vmWorkJobQueueService.addVmToNetworkThroughJobQueue(vm, network, requested); - final List pendingWorkJobs = _workJobDao.listPendingWorkJobs( - VirtualMachine.Type.Instance, vm.getId(), - VmWorkAddVmToNetwork.class.getName(), network.getUuid()); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vm.getUuid(), "addVmToNetwork"); - VmWorkJobVO workJob = null; - if (pendingWorkJobs != null && pendingWorkJobs.size() > 0) { - if (pendingWorkJobs.size() > 1) { - throw new CloudRuntimeException(String.format("The number of jobs to add network %s to vm %s are %d", network.getUuid(), vm.getInstanceName(), pendingWorkJobs.size())); + Object jobResult = vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + + if (jobResult != null && jobResult instanceof NicProfile) { + return (NicProfile) jobResult; } - workJob = pendingWorkJobs.get(0); - } else { - logger.trace("no jobs to add network {} for vm {} yet", network, vm); - workJob = createVmWorkJobToAddNetwork(vm, network, requested, context, user, account); + throw new RuntimeException("null job execution result"); } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmJobVirtualMachineOutcome(workJob, vm.getId()); } - private VmWorkJobVO createVmWorkJobToAddNetwork( - VirtualMachine vm, - Network network, - NicProfile requested, - CallContext context, - User user, - Account account) { - VmWorkJobVO workJob; - workJob = new VmWorkJobVO(context.getContextId()); - - workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); - workJob.setCmd(VmWorkAddVmToNetwork.class.getName()); + private NicProfile orchestrateAddVmToNetwork(final VirtualMachine vm, final Network network, final NicProfile requested) throws ConcurrentOperationException, ResourceUnavailableException, + InsufficientCapacityException { + return vmNetworkAttachmentOrchestrationService.addVmToNetwork(vm, network, requested, this); + } - workJob.setAccountId(account.getId()); - workJob.setUserId(user.getId()); - workJob.setVmType(VirtualMachine.Type.Instance); - workJob.setVmInstanceId(vm.getId()); - workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); - workJob.setSecondaryObjectIdentifier(network.getUuid()); + @Override + public NicTO toNicTO(final NicProfile nic, final HypervisorType hypervisorType) { + return vmNetworkAttachmentOrchestrationService.toNicTO(nic, hypervisorType); + } - // save work context info as there might be some duplicates - final VmWorkAddVmToNetwork workInfo = new VmWorkAddVmToNetwork(user.getId(), account.getId(), vm.getId(), - VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER, network.getId(), requested); - workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); + @Override + public boolean removeNicFromVm(final VirtualMachine vm, final Nic nic) + throws ConcurrentOperationException, ResourceUnavailableException { - try { - _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); - } catch (CloudRuntimeException e) { - if (e.getCause() instanceof EntityExistsException) { - String msg = String.format("A job to add a nic for network %s to vm %s already exists", network.getUuid(), vm.getUuid()); - logger.warn(msg, e); + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + return orchestrateRemoveNicFromVm(vm, nic); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } - throw e; - } - - return workJob; - } - - public Outcome removeNicFromVmThroughJobQueue( - final VirtualMachine vm, final Nic nic) { - Long vmId = vm.getId(); - String commandName = VmWorkRemoveNicFromVm.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); - VmWorkJobVO workJob = pendingWorkJob.first(); + } else { + final Outcome outcome = vmWorkJobQueueService.removeNicFromVmThroughJobQueue(vm, nic); - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vm.getUuid(), "removeNicFromVm"); - workJob = newVmWorkJobAndInfo.first(); - VmWorkRemoveNicFromVm workInfo = new VmWorkRemoveNicFromVm(newVmWorkJobAndInfo.second(), nic.getId()); + try { + Object jobResult = vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + if (jobResult != null && jobResult instanceof Boolean) { + return (Boolean) jobResult; + } + } catch (InsufficientCapacityException ex) { + throw new RuntimeException("Unexpected exception", ex); + } - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + throw new RuntimeException("Job failed with un-handled exception"); } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + } + + private boolean orchestrateRemoveNicFromVm(final VirtualMachine vm, final Nic nic) throws ConcurrentOperationException, ResourceUnavailableException { + return vmNetworkAttachmentOrchestrationService.removeNicFromVm(vm, nic, this); + } - return new VmJobVirtualMachineOutcome(workJob, vmId); + @Override + @DB + public boolean removeVmFromNetwork(final VirtualMachine vm, final Network network, final URI broadcastUri) throws ConcurrentOperationException, ResourceUnavailableException { + return orchestrateRemoveVmFromNetwork(vm, network, broadcastUri); } - public Outcome removeVmFromNetworkThroughJobQueue( - final VirtualMachine vm, final Network network, final URI broadcastUri) { - Long vmId = vm.getId(); - String commandName = VmWorkRemoveVmFromNetwork.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + @DB + private boolean orchestrateRemoveVmFromNetwork(final VirtualMachine vm, final Network network, final URI broadcastUri) throws ConcurrentOperationException, ResourceUnavailableException { + return vmNetworkAttachmentOrchestrationService.removeVmFromNetwork(vm, network, broadcastUri, this); + } - VmWorkJobVO workJob = pendingWorkJob.first(); + @Override + public void findHostAndMigrate(final String vmUuid, final Long newSvcOfferingId, final Map customParameters, final ExcludeList excludes) throws InsufficientCapacityException, ConcurrentOperationException, + ResourceUnavailableException { + vmScaleReconfigurationService.findHostAndMigrate(vmUuid, newSvcOfferingId, customParameters, excludes); + } - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + @Override + public void migrateForScale(final String vmUuid, final long srcHostId, final DeployDestination dest, final Long oldSvcOfferingId) + throws ResourceUnavailableException, ConcurrentOperationException { + vmScaleReconfigurationService.migrateForScale(vmUuid, srcHostId, dest, oldSvcOfferingId); + } - workJob = newVmWorkJobAndInfo.first(); - VmWorkRemoveVmFromNetwork workInfo = new VmWorkRemoveVmFromNetwork(newVmWorkJobAndInfo.second(), network, broadcastUri); + @Override + public boolean replugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final Host host) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + return vmNicBackendCommandService.replugNic(network, nic, vm, host); + } - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } + @Override + public boolean plugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final ReservationContext context, final DeployDestination dest) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + return vmNicBackendCommandService.plugNic(network, nic, vm, context, dest); + } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + @Override + public boolean unplugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final ReservationContext context, final DeployDestination dest) throws ConcurrentOperationException, + ResourceUnavailableException { + return vmNicBackendCommandService.unplugNic(network, nic, vm, context, dest); + } - return new VmJobVirtualMachineOutcome(workJob, vmId); + @Override + public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, + Map customParameters, final boolean reconfiguringOnExistingHost) + throws ResourceUnavailableException, InsufficientServerCapacityException, ConcurrentOperationException { + return vmScaleReconfigurationService.reConfigureVm(vmUuid, oldServiceOffering, newServiceOffering, customParameters, reconfiguringOnExistingHost); } - public Outcome reconfigureVmThroughJobQueue( - final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, Map customParameters, final boolean reconfiguringOnExistingHost) { - String commandName = VmWorkReconfigure.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + @Override + public String getConfigComponentName() { + return VirtualMachineManager.class.getSimpleName(); + } - VmWorkJobVO workJob = pendingWorkJob.first(); - Long vmId = pendingWorkJob.second(); + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { ClusterDeltaSyncInterval, StartRetry, VmDestroyForcestop, VmOpCancelInterval, VmOpCleanupInterval, VmOpCleanupWait, + VmOpLockStateRetry, VmOpWaitInterval, ExecuteInSequence, VmJobCheckInterval, VmJobTimeout, VmJobStateReportInterval, + VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, + HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, + AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName, + VmSyncPowerStateTransitioning, SystemVmEnableUserData + }; + } - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + public List getStoragePoolAllocators() { + return _storagePoolAllocators; + } - workJob = newVmWorkJobAndInfo.first(); - VmWorkReconfigure workInfo = new VmWorkReconfigure(newVmWorkJobAndInfo.second(), oldServiceOffering.getId(), newServiceOffering.getId(), customParameters, reconfiguringOnExistingHost); + @Inject + public void setStoragePoolAllocators(final List storagePoolAllocators) { + _storagePoolAllocators = storagePoolAllocators; + } - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + /** + * PowerState report handling for out-of-band changes and handling of left-over transitional VM states + */ - return new VmJobVirtualMachineOutcome(workJob, vmId); + @MessageHandler(topic = Topics.VM_POWER_STATE) + protected void HandlePowerStateReport(final String subject, final String senderAddress, final Object args) { + assert args != null; + vmPowerStateSyncManager.handlePowerStateReport((Long) args); } @ReflectionUse @@ -6052,7 +2656,7 @@ private Pair orchestrateMigrateWithStorage(final VmWorkM @ReflectionUse private Pair orchestrateMigrateForScale(final VmWorkMigrateForScale work) throws Exception { VMInstanceVO vm = findVmById(work.getVmId()); - orchestrateMigrateForScale(vm.getUuid(), + vmScaleReconfigurationService.orchestrateMigrateForScale(vm.getUuid(), work.getSrcHostId(), work.getDeployDestination(), work.getNewServiceOfferringId()); @@ -6062,7 +2666,7 @@ private Pair orchestrateMigrateForScale(final VmWorkMigr @ReflectionUse private Pair orchestrateReboot(final VmWorkReboot work) throws Exception { VMInstanceVO vm = findVmById(work.getVmId()); - orchestrateReboot(vm.getUuid(), work.getParams()); + vmRebootOrchestrationService.orchestrateReboot(vm.getUuid(), work.getParams()); return new Pair<>(JobInfo.Status.SUCCEEDED, null); } @@ -6121,51 +2725,24 @@ public Pair handleVmWorkJob(final VmWork work) throws Ex return _jobHandlerProxy.handleVmWorkJob(work); } - private VmWorkJobVO createPlaceHolderWork(final long instanceId) { - return createPlaceHolderWork(instanceId, null); - } - - private VmWorkJobVO createPlaceHolderWork(final long instanceId, String secondaryObjectIdentifier) { - final VmWorkJobVO workJob = new VmWorkJobVO(""); - - workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_PLACEHOLDER); - workJob.setCmd(""); - workJob.setCmdInfo(""); - - workJob.setAccountId(0); - workJob.setUserId(0); - workJob.setStep(VmWorkJobVO.Step.Starting); - workJob.setVmType(VirtualMachine.Type.Instance); - workJob.setVmInstanceId(instanceId); - if(org.apache.commons.lang3.StringUtils.isNotBlank(secondaryObjectIdentifier)) { - workJob.setSecondaryObjectIdentifier(secondaryObjectIdentifier); - } - workJob.setInitMsid(ManagementServerNode.getManagementServerId()); - - _workJobDao.persist(workJob); - - return workJob; - } @Override public UserVm restoreVirtualMachine(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, final Map details) throws ResourceUnavailableException, InsufficientCapacityException { final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { VmWorkJobVO placeHolder = null; - placeHolder = createPlaceHolderWork(vmId); + placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vmId); try { return orchestrateRestoreVirtualMachine(vmId, newTemplateId, rootDiskOfferingId, expunge, details); } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } } else { - final Outcome outcome = restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details); + final Outcome outcome = vmWorkJobQueueService.restoreVirtualMachineThroughJobQueue(vmId, newTemplateId, rootDiskOfferingId, expunge, details); - retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine"); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, String.valueOf(vmId), "restoreVirtualMachine"); - Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + Object jobResult = vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); if (jobResult != null && jobResult instanceof HashMap) { HashMap passwordMap = (HashMap)jobResult; @@ -6185,24 +2762,6 @@ private UserVm orchestrateRestoreVirtualMachine(final long vmId, final Long newT return _userVmService.restoreVirtualMachine(account, vmId, newTemplateId, rootDiskOfferingId, expunge, details); } - public Outcome restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, Map details) { - String commandName = VmWorkRestore.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId, rootDiskOfferingId, expunge, details); - - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmJobVirtualMachineOutcome(workJob, vmId); - } @ReflectionUse private Pair orchestrateRestoreVirtualMachine(final VmWorkRestore work) throws Exception { @@ -6218,21 +2777,19 @@ public Boolean updateDefaultNicForVM(final VirtualMachine vm, final Nic nic, fin final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { - VmWorkJobVO placeHolder = createPlaceHolderWork(vm.getId()); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); try { - return orchestrateUpdateDefaultNicForVM(vm, nic, defaultNic); + return vmNicUpdateService.updateDefaultNicForVM(vm, nic, defaultNic); } finally { - if (placeHolder != null) { - _workJobDao.expunge(placeHolder.getId()); - } + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); } } else { - final Outcome outcome = updateDefaultNicForVMThroughJobQueue(vm, nic, defaultNic); + final Outcome outcome = vmWorkJobQueueService.updateDefaultNicForVMThroughJobQueue(vm, nic, defaultNic); - retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateDefaultNicForVM"); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateDefaultNicForVM"); try { - Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + Object jobResult = vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); if (jobResult != null && jobResult instanceof Boolean) { return (Boolean)jobResult; @@ -6245,44 +2802,6 @@ public Boolean updateDefaultNicForVM(final VirtualMachine vm, final Nic nic, fin } } - private Boolean orchestrateUpdateDefaultNicForVM(final VirtualMachine vm, final Nic nic, final Nic defaultNic) { - - logger.debug("Updating default nic of vm {} from nic {} to nic {}", vm, defaultNic.getUuid(), nic.getUuid()); - Integer chosenID = nic.getDeviceId(); - Integer existingID = defaultNic.getDeviceId(); - NicVO nicVO = _nicsDao.findById(nic.getId()); - NicVO defaultNicVO = _nicsDao.findById(defaultNic.getId()); - - nicVO.setDefaultNic(true); - nicVO.setDeviceId(existingID); - defaultNicVO.setDefaultNic(false); - defaultNicVO.setDeviceId(chosenID); - - _nicsDao.persist(nicVO); - _nicsDao.persist(defaultNicVO); - return true; - } - - public Outcome updateDefaultNicForVMThroughJobQueue(final VirtualMachine vm, final Nic nic, final Nic defaultNic) { - Long vmId = vm.getId(); - String commandName = VmWorkUpdateDefaultNic.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkUpdateDefaultNic workInfo = new VmWorkUpdateDefaultNic(newVmWorkJobAndInfo.second(), nic.getId(), defaultNic.getId()); - - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmJobVirtualMachineOutcome(workJob, vmId); - } - @ReflectionUse private Pair orchestrateUpdateDefaultNic(final VmWorkUpdateDefaultNic work) throws Exception { VMInstanceVO vm = findVmById(work.getVmId()); @@ -6294,19 +2813,19 @@ private Pair orchestrateUpdateDefaultNic(final VmWorkUpd if (defaultNic == null) { throw new CloudRuntimeException("Unable to find default nic " + work.getDefaultNicId()); } - final boolean result = orchestrateUpdateDefaultNicForVM(vm, nic, defaultNic); + final boolean result = vmNicUpdateService.updateDefaultNicForVM(vm, nic, defaultNic); return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result)); } @Override public boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) { - Outcome outcome = updateVmNicThroughJobQueue(vm, nic, enabled); + Outcome outcome = vmWorkJobQueueService.updateVmNicThroughJobQueue(vm, nic, enabled); - retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateVmNic"); + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateVmNic"); try { - Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + Object jobResult = vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); if (jobResult instanceof Boolean) { return BooleanUtils.isTrue((Boolean) jobResult); } @@ -6316,52 +2835,6 @@ public boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) { throw new CloudRuntimeException("Unexpected job execution result."); } - private boolean orchestrateUpdateVmNic(final VirtualMachine vm, final Nic nic, final Boolean enabled) throws ResourceUnavailableException { - if (vm.getState() == State.Running) { - try { - UpdateVmNicCommand updateVmNicCmd = new UpdateVmNicCommand(nic.getMacAddress(), vm.getName(), enabled); - Commands cmds = new Commands(Command.OnError.Stop); - cmds.addCommand("updatevmnic", updateVmNicCmd); - - _agentMgr.send(vm.getHostId(), cmds); - - UpdateVmNicAnswer updateVmNicAnswer = cmds.getAnswer(UpdateVmNicAnswer.class); - if (updateVmNicAnswer == null || !updateVmNicAnswer.getResult()) { - logger.warn("Unable to update VM %s NIC [{}].", vm.getName(), nic.getUuid()); - return false; - } - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException(String.format("Unable to update NIC %s for VM %s.", nic.getUuid(), vm.getUuid()), vm.getHostId(), e); - } - } - - NicVO nicVo = _nicsDao.findById(nic.getId()); - nicVo.setEnabled(enabled); - _nicsDao.persist(nicVo); - - return true; - } - - public Outcome updateVmNicThroughJobQueue(final VirtualMachine vm, final Nic nic, final Boolean isNicEnabled) { - Long vmId = vm.getId(); - String commandName = VmWorkUpdateNic.class.getName(); - Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); - - VmWorkJobVO workJob = pendingWorkJob.first(); - - if (workJob == null) { - Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); - - workJob = newVmWorkJobAndInfo.first(); - VmWorkUpdateNic workInfo = new VmWorkUpdateNic(newVmWorkJobAndInfo.second(), nic.getId(), isNicEnabled); - - setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); - } - AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); - - return new VmJobVirtualMachineOutcome(workJob, vmId); - } - @ReflectionUse private Pair orchestrateUpdateVmNic(final VmWorkUpdateNic work) throws Exception { VMInstanceVO vm = findVmById(work.getVmId()); @@ -6369,180 +2842,18 @@ private Pair orchestrateUpdateVmNic(final VmWorkUpdateNi if (nic == null) { throw new CloudRuntimeException(String.format("Unable to find NIC with ID %s.", work.getNicId())); } - final boolean result = orchestrateUpdateVmNic(vm, nic, work.isEnabled()); + final boolean result = vmNicUpdateService.updateVmNic(vm, nic, work.isEnabled()); return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result)); } - private Pair findClusterAndHostIdForVmFromVolumes(long vmId) { - Long clusterId = null; - Long hostId = null; - List volumes = _volsDao.findByInstance(vmId); - for (VolumeVO volume : volumes) { - if (Volume.State.Ready.equals(volume.getState()) && - volume.getPoolId() != null) { - StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); - if (pool != null && pool.getClusterId() != null) { - clusterId = pool.getClusterId(); - // hostId to be used only for sending commands, capacity check skipped - List hosts = _hostDao.findHypervisorHostInCluster(pool.getClusterId()); - if (CollectionUtils.isNotEmpty(hosts)) { - hostId = hosts.get(0).getId(); - break; - } - } - } - } - return new Pair<>(clusterId, hostId); - } - @Override public Pair findClusterAndHostIdForVm(VirtualMachine vm, boolean skipCurrentHostForStartingVm) { - Long hostId = null; - Host host = null; - if (!skipCurrentHostForStartingVm || !State.Starting.equals(vm.getState())) { - hostId = vm.getHostId(); - } - Long clusterId = null; - if (hostId == null) { - if (vm.getLastHostId() == null) { - return findClusterAndHostIdForVmFromVolumes(vm.getId()); - } - hostId = vm.getLastHostId(); - host = _hostDao.findById(hostId); - logger.debug("host id is null, using last host {} with id {}", host, hostId); - } - host = host == null ? _hostDao.findById(hostId) : host; - if (host != null) { - clusterId = host.getClusterId(); - return new Pair<>(clusterId, hostId); - } - return findClusterAndHostIdForVmFromVolumes(vm.getId()); - } - - private Pair findClusterAndHostIdForVm(VirtualMachine vm) { - return findClusterAndHostIdForVm(vm, false); + return vmDiskOfferingSuitabilityService.findClusterAndHostIdForVm(vm, skipCurrentHostForStartingVm); } @Override public Pair findClusterAndHostIdForVm(long vmId) { - VMInstanceVO vm = _vmDao.findById(vmId); - if (vm == null) { - return new Pair<>(null, null); - } - return findClusterAndHostIdForVm(vm); - } - - protected VirtualMachine retrieveVmFromJobOutcome(Outcome jobOutcome, String vmUuid, String jobName) { - try { - return jobOutcome.get(); - } catch (InterruptedException | java.util.concurrent.ExecutionException e) { - throw new RuntimeException(String.format("Unable to retrieve result from job \"%s\" due to [%s]. VM {\"uuid\": \"%s\"}.", jobName, e.getMessage(), vmUuid), e); - } - } - - protected Object retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(Outcome outcome) throws ResourceUnavailableException, InsufficientCapacityException{ - Object jobResult = _jobMgr.unmarshallResultObject(outcome.getJob()); - - if (jobResult == null) { - return null; - } - - if (jobResult instanceof AgentUnavailableException) { - throw (AgentUnavailableException) jobResult; - } - - if (jobResult instanceof InsufficientServerCapacityException) { - throw (InsufficientServerCapacityException) jobResult; - } - - if (jobResult instanceof ResourceUnavailableException) { - throw (ResourceUnavailableException) jobResult; - } - - if (jobResult instanceof InsufficientCapacityException) { - throw (InsufficientCapacityException) jobResult; - } - - if (jobResult instanceof ConcurrentOperationException) { - throw (ConcurrentOperationException) jobResult; - } - - if (jobResult instanceof RuntimeException) { - throw (RuntimeException) jobResult; - } - - if (jobResult instanceof Throwable) { - throw new RuntimeException("Unexpected exception", (Throwable)jobResult); - } - - return jobResult; - } - - protected Pair retrievePendingWorkJob(String vmUuid, String commandName) { - return retrievePendingWorkJob(null, vmUuid, VirtualMachine.Type.Instance, commandName); - } - - protected Pair retrievePendingWorkJob(Long id, String commandName) { - return retrievePendingWorkJob(id, null, VirtualMachine.Type.Instance, commandName); - } - - protected Pair retrievePendingWorkJob(Long vmId, String vmUuid, VirtualMachine.Type vmType, String commandName) { - if (vmId == null) { - VMInstanceVO vm = _vmDao.findByUuid(vmUuid); - - if (vm == null) { - String message = String.format("Could not find a VM with the uuid [%s]. Unable to continue validations with command [%s] through job queue.", vmUuid, commandName); - logger.error(message); - throw new RuntimeException(message); - } - - vmId = vm.getId(); - - if (vmType == null) { - vmType = vm.getType(); - } - } - - List pendingWorkJobs = _workJobDao.listPendingWorkJobs(vmType, vmId, commandName); - - if (CollectionUtils.isNotEmpty(pendingWorkJobs)) { - return new Pair<>(pendingWorkJobs.get(0), vmId); - } - - return new Pair<>(null, vmId); - } - - protected Pair createWorkJobAndWorkInfo(String commandName, Long vmId) { - return createWorkJobAndWorkInfo(commandName, null, vmId); - } - - protected Pair createWorkJobAndWorkInfo(String commandName, VmWorkJobVO.Step step, Long vmId) { - CallContext context = CallContext.current(); - long userId = context.getCallingUser().getId(); - long accountId = context.getCallingAccount().getId(); - - VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); - workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); - workJob.setCmd(commandName); - workJob.setAccountId(accountId); - workJob.setUserId(userId); - - if (step != null) { - workJob.setStep(step); - } - - workJob.setVmType(VirtualMachine.Type.Instance); - workJob.setVmInstanceId(vmId); - workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); - - VmWork workInfo = new VmWork(userId, accountId, vmId, VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER); - - return new Pair<>(workJob, workInfo); - } - - protected void setCmdInfoAndSubmitAsyncJob(VmWorkJobVO workJob, VmWork workInfo, Long vmId) { - workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); - _jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vmId); + return vmDiskOfferingSuitabilityService.findClusterAndHostIdForVm(vmId); } protected VMInstanceVO findVmById(Long vmId) { @@ -6558,129 +2869,31 @@ protected VMInstanceVO findVmById(Long vmId) { @Override public HashMap getVirtualMachineStatistics(Host host, List vmIds) { - HashMap vmStatsById = new HashMap<>(); - if (CollectionUtils.isEmpty(vmIds)) { - return vmStatsById; - } - Map vmMap = _vmDao.getNameIdMapForVmIds(vmIds); - return getVirtualMachineStatistics(host, vmMap); + return vmStatsCollector.getVirtualMachineStatistics(host, vmIds); } @Override public HashMap getVirtualMachineStatistics(Host host, Map vmInstanceNameIdMap) { - HashMap vmStatsById = new HashMap<>(); - if (MapUtils.isEmpty(vmInstanceNameIdMap)) { - return vmStatsById; - } - Answer answer = _agentMgr.easySend(host.getId(), new GetVmStatsCommand( - new ArrayList<>(vmInstanceNameIdMap.keySet()), host.getGuid(), host.getName())); - if (answer == null || !answer.getResult()) { - logger.warn("Unable to obtain VM statistics."); - return vmStatsById; - } else { - HashMap vmStatsByName = ((GetVmStatsAnswer)answer).getVmStatsMap(); - if (vmStatsByName == null) { - logger.warn("Unable to obtain VM statistics."); - return vmStatsById; - } - for (Map.Entry entry : vmStatsByName.entrySet()) { - vmStatsById.put(vmInstanceNameIdMap.get(entry.getKey()), entry.getValue()); - } - } - return vmStatsById; + return vmStatsCollector.getVirtualMachineStatistics(host, vmInstanceNameIdMap); } @Override public HashMap> getVmDiskStatistics(Host host, Map vmInstanceNameIdMap) { - HashMap> vmDiskStatsById = new HashMap<>(); - if (MapUtils.isEmpty(vmInstanceNameIdMap)) { - return vmDiskStatsById; - } - Answer answer = _agentMgr.easySend(host.getId(), new GetVmDiskStatsCommand( - new ArrayList<>(vmInstanceNameIdMap.keySet()), host.getGuid(), host.getName())); - if (answer == null || !answer.getResult()) { - logger.warn("Unable to obtain VM disk statistics."); - return vmDiskStatsById; - } else { - HashMap> vmDiskStatsByName = ((GetVmDiskStatsAnswer)answer).getVmDiskStatsMap(); - if (vmDiskStatsByName == null) { - logger.warn("Unable to obtain VM disk statistics."); - return vmDiskStatsById; - } - for (Map.Entry> entry: vmDiskStatsByName.entrySet()) { - vmDiskStatsById.put(vmInstanceNameIdMap.get(entry.getKey()), entry.getValue()); - } - } - return vmDiskStatsById; + return vmStatsCollector.getVmDiskStatistics(host, vmInstanceNameIdMap); } @Override public HashMap> getVmNetworkStatistics(Host host, Map vmInstanceNameIdMap) { - HashMap> vmNetworkStatsById = new HashMap<>(); - if (MapUtils.isEmpty(vmInstanceNameIdMap)) { - return vmNetworkStatsById; - } - Answer answer = _agentMgr.easySend(host.getId(), new GetVmNetworkStatsCommand( - new ArrayList<>(vmInstanceNameIdMap.keySet()), host.getGuid(), host.getName())); - if (answer == null || !answer.getResult()) { - logger.warn("Unable to obtain VM network statistics."); - return vmNetworkStatsById; - } else { - HashMap> vmNetworkStatsByName = ((GetVmNetworkStatsAnswer)answer).getVmNetworkStatsMap(); - if (vmNetworkStatsByName == null) { - logger.warn("Unable to obtain VM network statistics."); - return vmNetworkStatsById; - } - for (Map.Entry> entry: vmNetworkStatsByName.entrySet()) { - vmNetworkStatsById.put(vmInstanceNameIdMap.get(entry.getKey()), entry.getValue()); - } - } - return vmNetworkStatsById; + return vmStatsCollector.getVmNetworkStatistics(host, vmInstanceNameIdMap); } protected boolean isDiskOfferingSuitableForVm(VMInstanceVO vm, VirtualMachineProfile profile, long podId, long clusterId, long hostId, long diskOfferingId) { - - DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); - VolumeVO dummyVolume = new VolumeVO("Data", vm.getDataCenterId(), podId, vm.getAccountId(), - vm.getDomainId(), vm.getId(), null, null, diskOffering.getProvisioningType(), diskOffering.getDiskSize(), Type.DATADISK); - try { - Field idField = dummyVolume.getClass().getDeclaredField("id"); - idField.setAccessible(true); - idField.set(dummyVolume, Volume.DISK_OFFERING_SUITABILITY_CHECK_VOLUME_ID); - } catch (NoSuchFieldException | IllegalAccessException ignored) { - return false; - } - dummyVolume.setDiskOfferingId(diskOfferingId); - DiskProfile diskProfile = new DiskProfile(dummyVolume, diskOffering, profile.getHypervisorType()); - diskProfile.setMinIops(diskOffering.getMinIops()); - diskProfile.setMaxIops(diskOffering.getMaxIops()); - ExcludeList avoid = new ExcludeList(); - DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), podId, clusterId, hostId, null, null); - for (StoragePoolAllocator allocator : _storagePoolAllocators) { - List poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, 1); - if (CollectionUtils.isNotEmpty(poolListFromAllocator)) { - logger.debug("Found a suitable pool: {} for disk offering: {}", poolListFromAllocator.get(0).getName(), diskOffering.getName()); - return true; - } - } - return false; + return vmDiskOfferingSuitabilityService.isDiskOfferingSuitableForVm(vm, profile, podId, clusterId, hostId, diskOfferingId); } @Override public Map getDiskOfferingSuitabilityForVm(long vmId, List diskOfferingIds) { - VMInstanceVO vm = _vmDao.findById(vmId); - if (vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_VM) != null) { - return new HashMap<>(); - } - VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); - Pair clusterAndHost = findClusterAndHostIdForVm(vm, false); - Long clusterId = clusterAndHost.first(); - Cluster cluster = _clusterDao.findById(clusterId); - Map result = new HashMap<>(); - for (Long diskOfferingId : diskOfferingIds) { - result.put(diskOfferingId, isDiskOfferingSuitableForVm(vm, profile, cluster.getPodId(), clusterId, clusterAndHost.second(), diskOfferingId)); - } - return result; + return vmDiskOfferingSuitabilityService.getDiskOfferingSuitabilityForVm(vmId, diskOfferingIds); } @Override diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java index 475ed0f37bd2..49b500884443 100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachinePowerStateSyncImpl.java @@ -23,7 +23,7 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.PublishScope; diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmAllocationOrchestrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmAllocationOrchestrationService.java new file mode 100644 index 000000000000..70faba178f85 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmAllocationOrchestrationService.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; +import com.cloud.offering.ServiceOffering; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; + +public interface VmAllocationOrchestrationService { + + void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, + DiskOfferingInfo rootDiskOfferingInfo, List dataDiskOfferings, List dataDiskDeviceIds, + LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, + HypervisorType hyperType, Map> extraDhcpOptions, + Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) + throws InsufficientCapacityException; + + void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, + LinkedHashMap> networks, DeploymentPlan plan, + HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; + + void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, + Account owner, Long rootDiskSizeFinal, Volume volume, Snapshot snapshot); + + void checkIfTemplateNeededForCreatingVmVolumes(VMInstanceVO vm); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmAllocationOrchestrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmAllocationOrchestrationServiceImpl.java new file mode 100644 index 000000000000..2a1cc24879c0 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmAllocationOrchestrationServiceImpl.java @@ -0,0 +1,227 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; +import com.cloud.offering.ServiceOffering; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VMTemplateZoneVO; +import com.cloud.storage.Volume; +import com.cloud.storage.Volume.Type; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplateZoneDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.utils.fsm.StateMachine2; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmAllocationOrchestrationServiceImpl implements VmAllocationOrchestrationService { + + private static final Logger logger = LogManager.getLogger(VmAllocationOrchestrationServiceImpl.class); + + @Inject + protected VMInstanceDao vmDao; + @Inject + protected EntityManager entityMgr; + @Inject + protected NetworkOrchestrationService networkMgr; + @Inject + protected VolumeOrchestrationService volumeMgr; + @Inject + protected DiskOfferingDao diskOfferingDao; + @Inject + protected VMTemplateDao templateDao; + @Inject + protected VMTemplateZoneDao templateZoneDao; + @Inject + protected VolumeDao volsDao; + protected StateMachine2 stateMachine = State.getStateMachine(); + + @Override + public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, + final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, List dataDiskDeviceIds, + final LinkedHashMap> auxiliaryNetworks, final DeploymentPlan plan, + final HypervisorType hyperType, final Map> extraDhcpOptions, + final Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) + throws InsufficientCapacityException { + + logger.info("Allocating Instance from Template: {} with hostname: {} and {} networks", template, vmInstanceName, auxiliaryNetworks.size()); + VMInstanceVO persistedVm = null; + try { + final VMInstanceVO vm = vmDao.findVMByInstanceName(vmInstanceName); + final Account owner = entityMgr.findById(Account.class, vm.getAccountId()); + + logger.debug("Allocating entries for VM: " + vm); + + vm.setDataCenterId(plan.getDataCenterId()); + if (plan.getPodId() != null) { + vm.setPodIdToDeployIn(plan.getPodId()); + } + assert plan.getClusterId() == null && plan.getPoolId() == null : "We currently don't support cluster and pool preset yet"; + persistedVm = vmDao.persist(vm); + + final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(persistedVm, template, serviceOffering, null, null); + + Long rootDiskSize = rootDiskOfferingInfo.getSize(); + if (vm.getType().isUsedBySystem() && VirtualMachineManagerImpl.SystemVmRootDiskSize.value() != null + && VirtualMachineManagerImpl.SystemVmRootDiskSize.value() > 0L) { + rootDiskSize = VirtualMachineManagerImpl.SystemVmRootDiskSize.value(); + } + final Long rootDiskSizeFinal = rootDiskSize; + + logger.debug("Allocating NICs for {}", persistedVm); + + try { + if (!vmProfile.getBootArgs().contains("ExternalLoadBalancerVm")) { + networkMgr.allocate(vmProfile, auxiliaryNetworks, extraDhcpOptions); + } + } catch (final ConcurrentOperationException e) { + throw new CloudRuntimeException("Concurrent operation while trying to allocate resources for the VM", e); + } + + logger.debug("Allocating disks for {}", persistedVm); + + allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot); + + CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); + try { + if (dataDiskOfferings != null) { + int index = 0; + for (final DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { + Long deviceId = dataDiskDeviceIds.get(index++); + String volumeName = deviceId == null ? "DATA-" + persistedVm.getId() : "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId); + volumeMgr.allocateRawVolume(Type.DATADISK, volumeName, dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), + dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, true); + } + } + if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { + Long diskNumber = 1L; + for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { + DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); + long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); + VMTemplateVO dataDiskTemplate = templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(diskNumber), diskOffering, diskOfferingSize, null, null, + persistedVm, dataDiskTemplate, owner, diskNumber, true); + diskNumber++; + } + } + } finally { + CallContext.unregister(); + } + + logger.debug("Allocation completed for VM: " + persistedVm); + } catch (InsufficientCapacityException | CloudRuntimeException e) { + try { + if (persistedVm != null) { + stateTransitTo(persistedVm, Event.OperationFailedToError, null); + } + } catch (NoTransitionException nte) { + logger.error("Failed to transition {} in {} state to Error state", persistedVm, persistedVm.getState().toString()); + } + throw e; + } + } + + @Override + public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, + final LinkedHashMap> networks, final DeploymentPlan plan, + final HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { + DiskOffering diskOffering = diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); + allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), new ArrayList<>(), networks, plan, hyperType, null, null, volume, snapshot); + } + + @Override + public void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, + Account owner, Long rootDiskSizeFinal, Volume volume, Snapshot snapshot) { + CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); + try { + String rootVolumeName = String.format("ROOT-%s", vm.getId()); + if (template.getFormat() == ImageFormat.ISO) { + volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(), + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, true); + } else if (Arrays.asList(ImageFormat.BAREMETAL, ImageFormat.EXTERNAL).contains(template.getFormat())) { + logger.debug("{} has format [{}]. Skipping ROOT volume [{}] allocation.", template, template.getFormat(), rootVolumeName); + } else { + volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal, + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, volume, snapshot); + } + } finally { + CallContext.unregister(); + } + } + + @Override + public void checkIfTemplateNeededForCreatingVmVolumes(VMInstanceVO vm) { + final List existingRootVolumes = volsDao.findReadyRootVolumesByInstance(vm.getId()); + if (CollectionUtils.isNotEmpty(existingRootVolumes)) { + return; + } + final VMTemplateVO template = templateDao.findById(vm.getTemplateId()); + if (template == null) { + String msg = "Template for the VM instance can not be found, VM instance configuration needs to be updated"; + logger.error("{}. Template ID: {} seems to be removed", msg, vm.getTemplateId()); + throw new CloudRuntimeException(msg); + } + final VMTemplateZoneVO templateZoneVO = templateZoneDao.findByZoneTemplate(vm.getDataCenterId(), template.getId()); + if (templateZoneVO == null) { + String msg = "Template for the VM instance can not be found in the zone ID: %s, VM instance configuration needs to be updated"; + logger.error("{}. {}", msg, template); + throw new CloudRuntimeException(msg); + } + } + + protected boolean stateTransitTo(final VMInstanceVO vm, final Event event, final Long hostId) throws NoTransitionException { + return stateMachine.transitTo(vm, event, new Pair<>(vm.getHostId(), hostId), vmDao); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmCommandSpecPostProcessingService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmCommandSpecPostProcessingService.java new file mode 100644 index 000000000000..f2272ef388d2 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmCommandSpecPostProcessingService.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; + +public interface VmCommandSpecPostProcessingService { + + void setEnterSetupMode(VirtualMachineTO vmTo, Map params); + + void addExtraConfig(VirtualMachineTO vmTO); + + void prepareManagedDiskPaths(DiskTO[] disks, HypervisorType hypervisorType); + + void applyStartAnswerDiskMetadata(DiskTO[] disks, Map> iqnToData); + + void syncDiskChainChange(StartAnswer answer); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmCommandSpecPostProcessingServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmCommandSpecPostProcessingServiceImpl.java new file mode 100644 index 000000000000..1b6d7ebdb95c --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmCommandSpecPostProcessingServiceImpl.java @@ -0,0 +1,164 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; + +@Component +public class VmCommandSpecPostProcessingServiceImpl implements VmCommandSpecPostProcessingService { + + private static final Logger logger = LogManager.getLogger(VmCommandSpecPostProcessingServiceImpl.class); + + @Inject + protected VolumeDao volumeDao; + @Inject + protected VolumeOrchestrationService volumeMgr; + + @Override + public void setEnterSetupMode(VirtualMachineTO vmTo, Map params) { + Boolean enterSetup = null; + if (params != null) { + enterSetup = (Boolean) params.get(VirtualMachineProfile.Param.BootIntoSetup); + } + logger.debug("Orchestrating VM reboot for '{}' {} set to {}", vmTo.getName(), VirtualMachineProfile.Param.BootIntoSetup, enterSetup); + vmTo.setEnterHardwareSetup(enterSetup == null ? false : enterSetup); + } + + @Override + public void addExtraConfig(VirtualMachineTO vmTO) { + Map details = vmTO.getDetails(); + for (String key : details.keySet()) { + if (key.startsWith(ApiConstants.EXTRA_CONFIG)) { + vmTO.addExtraConfig(key, details.get(key)); + } + } + } + + @Override + public void prepareManagedDiskPaths(final DiskTO[] disks, final HypervisorType hypervisorType) { + if (hypervisorType != HypervisorType.KVM) { + return; + } + + if (disks != null) { + for (final DiskTO disk : disks) { + final Map details = disk.getDetails(); + final boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED)); + + if (isManaged && disk.getPath() == null) { + final Long volumeId = disk.getData().getId(); + final VolumeVO volume = volumeDao.findById(volumeId); + + disk.setPath(volume.get_iScsiName()); + + if (disk.getData() instanceof VolumeObjectTO) { + final VolumeObjectTO volTo = (VolumeObjectTO)disk.getData(); + + volTo.setPath(volume.get_iScsiName()); + } + + volume.setPath(volume.get_iScsiName()); + + volumeDao.update(volumeId, volume); + } + } + } + } + + @Override + public void applyStartAnswerDiskMetadata(final DiskTO[] disks, final Map> iqnToData) { + if (disks != null && iqnToData != null) { + for (final DiskTO disk : disks) { + final Map details = disk.getDetails(); + final boolean isManaged = details != null && Boolean.parseBoolean(details.get(DiskTO.MANAGED)); + + if (isManaged) { + final Long volumeId = disk.getData().getId(); + final VolumeVO volume = volumeDao.findById(volumeId); + final String iScsiName = volume.get_iScsiName(); + + boolean update = false; + + final Map data = iqnToData.get(iScsiName); + + if (data != null) { + final String path = data.get(StartAnswer.PATH); + + if (path != null) { + volume.setPath(path); + + update = true; + } + + final String imageFormat = data.get(StartAnswer.IMAGE_FORMAT); + + if (imageFormat != null) { + volume.setFormat(ImageFormat.valueOf(imageFormat)); + + update = true; + } + + if (update) { + volumeDao.update(volumeId, volume); + } + } + } + } + } + } + + @Override + public void syncDiskChainChange(final StartAnswer answer) { + final VirtualMachineTO vmSpec = answer.getVirtualMachine(); + + for (final DiskTO disk : vmSpec.getDisks()) { + if (disk.getType() != Volume.Type.ISO) { + final VolumeObjectTO vol = (VolumeObjectTO)disk.getData(); + final VolumeVO volume = volumeDao.findById(vol.getId()); + if (vmSpec.getDeployAsIsInfo() != null && org.apache.commons.lang3.StringUtils.isNotBlank(vol.getPath())) { + volume.setPath(vol.getPath()); + volumeDao.update(volume.getId(), volume); + } + + if(vol.getPath() != null) { + volumeMgr.updateVolumeDiskChain(vol.getId(), vol.getPath(), vol.getChainInfo(), vol.getUpdatedDataStoreUUID()); + } else { + volumeMgr.updateVolumeDiskChain(vol.getId(), volume.getPath(), vol.getChainInfo(), vol.getUpdatedDataStoreUUID()); + } + } + } + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmDestroyOrchestrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmDestroyOrchestrationService.java new file mode 100644 index 000000000000..9b57d4b58ee0 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmDestroyOrchestrationService.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; + +public interface VmDestroyOrchestrationService { + + void destroy(String vmUuid, boolean expunge) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException; + + void deleteVMSnapshots(VMInstanceVO vm, boolean expunge); + + boolean checkVmOnHost(VirtualMachine vm, long hostId) throws AgentUnavailableException, OperationTimedoutException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmDestroyOrchestrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmDestroyOrchestrationServiceImpl.java new file mode 100644 index 000000000000..3de983e21770 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmDestroyOrchestrationServiceImpl.java @@ -0,0 +1,179 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.gpu.GpuService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckVirtualMachineAnswer; +import com.cloud.agent.api.CheckVirtualMachineCommand; +import com.cloud.agent.api.RestoreVMSnapshotAnswer; +import com.cloud.agent.api.RestoreVMSnapshotCommand; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithExceptionNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotManager; +import com.cloud.vm.snapshot.VMSnapshotVO; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; + +@Component +public class VmDestroyOrchestrationServiceImpl implements VmDestroyOrchestrationService { + + private static final Logger logger = LogManager.getLogger(VmDestroyOrchestrationServiceImpl.class); + + @Inject + protected VMInstanceDao vmDao; + @Inject + protected UserVmDao userVmDao; + @Inject + protected VMSnapshotDao vmSnapshotDao; + @Inject + protected VMSnapshotManager vmSnapshotMgr; + @Inject + protected AgentManager agentMgr; + @Inject + protected GpuService gpuService; + @Inject + protected BackupManager backupManager; + @Inject + @Lazy + protected VirtualMachineManager virtualMachineManager; + + @Override + public void destroy(final String vmUuid, final boolean expunge) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + VMInstanceVO vm = vmDao.findByUuid(vmUuid); + if (vm == null || vm.getState() == State.Destroyed || vm.getState() == State.Expunging || vm.getRemoved() != null) { + logger.debug("Unable to find vm or vm is destroyed: {}", vm); + return; + } + + logger.debug("Destroying vm {}, expunge flag {}", vm, (expunge ? "on" : "off")); + + advanceStop(vmUuid); + + deleteVMSnapshots(vm, expunge); + + gpuService.deallocateAllGpuDevicesForVm(vm.getId()); + + Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { + @Override + public void doInTransactionWithoutResult(final TransactionStatus status) throws CloudRuntimeException { + VMInstanceVO vm = vmDao.findByUuid(vmUuid); + try { + if (!virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.DestroyRequested, vm.getHostId())) { + logger.debug("Unable to destroy the vm because it is not in the correct state: {}", vm); + throw new CloudRuntimeException("Unable to destroy " + vm); + } else { + if (expunge) { + backupManager.checkAndRemoveBackupOfferingBeforeExpunge(vm); + if (!virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, vm.getHostId())) { + logger.debug("Unable to expunge the vm because it is not in the correct state: {}", vm); + throw new CloudRuntimeException("Unable to expunge " + vm); + } + } + } + } catch (final NoTransitionException e) { + String message = String.format("Unable to destroy %s due to [%s].", vm.toString(), e.getMessage()); + logger.debug(message, e); + throw new CloudRuntimeException(message, e); + } + } + }); + } + + private void advanceStop(final String vmUuid) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + try { + virtualMachineManager.advanceStop(vmUuid, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + } catch (ResourceUnavailableException e) { + if (e instanceof AgentUnavailableException) { + throw (AgentUnavailableException)e; + } + throw new CloudRuntimeException("Unable to stop vm " + vmUuid, e); + } + } + + /** + * Delete vm snapshots depending on vm's hypervisor type. For Vmware, vm snapshots removal is delegated to vm cleanup thread + * to reduce tasks sent to hypervisor (one tasks to delete vm snapshots and vm itself + * instead of one task for each vm snapshot plus another for the vm) + * @param vm vm + * @param expunge indicates if vm should be expunged + */ + @Override + public void deleteVMSnapshots(VMInstanceVO vm, boolean expunge) { + if (!vm.getHypervisorType().equals(HypervisorType.VMware)) { + if (!vmSnapshotMgr.deleteAllVMSnapshots(vm.getId(), null)) { + logger.debug("Unable to delete all Snapshots for {}", vm); + throw new CloudRuntimeException("Unable to delete Instance Snapshots for " + vm); + } + } else { + if (expunge) { + vmSnapshotMgr.deleteVMSnapshotsFromDB(vm.getId(), false); + } + } + } + + @Override + public boolean checkVmOnHost(final VirtualMachine vm, final long hostId) throws AgentUnavailableException, OperationTimedoutException { + final Answer answer = agentMgr.send(hostId, new CheckVirtualMachineCommand(vm.getInstanceName())); + if (answer == null || !answer.getResult()) { + return false; + } + if (answer instanceof CheckVirtualMachineAnswer) { + final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer)answer; + if (vmAnswer.getState() == PowerState.PowerOff) { + return false; + } + } + + UserVmVO userVm = userVmDao.findById(vm.getId()); + if (userVm != null) { + List vmSnapshots = vmSnapshotDao.findByVm(vm.getId()); + RestoreVMSnapshotCommand command = vmSnapshotMgr.createRestoreCommand(userVm, vmSnapshots); + if (command != null) { + RestoreVMSnapshotAnswer restoreVMSnapshotAnswer = (RestoreVMSnapshotAnswer) agentMgr.send(hostId, command); + if (restoreVMSnapshotAnswer == null || !restoreVMSnapshotAnswer.getResult()) { + logger.warn("Unable to restore the Instance Snapshot from image file after live migration of Instance with vmsnapshots: {}", restoreVMSnapshotAnswer == null ? "null answer" : restoreVMSnapshotAnswer.getDetails()); + } + } + } + + return true; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmDiskOfferingSuitabilityService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmDiskOfferingSuitabilityService.java new file mode 100644 index 000000000000..fea519bf376f --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmDiskOfferingSuitabilityService.java @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; +import java.util.Map; + +import com.cloud.utils.Pair; + +public interface VmDiskOfferingSuitabilityService { + + Pair findClusterAndHostIdForVm(VirtualMachine vm, boolean skipCurrentHostForStartingVm); + + Pair findClusterAndHostIdForVm(long vmId); + + boolean isDiskOfferingSuitableForVm(VMInstanceVO vm, VirtualMachineProfile profile, long podId, long clusterId, long hostId, long diskOfferingId); + + Map getDiskOfferingSuitabilityForVm(long vmId, List diskOfferingIds); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmDiskOfferingSuitabilityServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmDiskOfferingSuitabilityServiceImpl.java new file mode 100644 index 000000000000..52fd04ece949 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmDiskOfferingSuitabilityServiceImpl.java @@ -0,0 +1,187 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.org.Cluster; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + +@Component +public class VmDiskOfferingSuitabilityServiceImpl implements VmDiskOfferingSuitabilityService { + + private static final Logger logger = LogManager.getLogger(VmDiskOfferingSuitabilityServiceImpl.class); + + @Inject + protected VMInstanceDao vmInstanceDao; + @Inject + protected VMInstanceDetailsDao vmInstanceDetailsDao; + @Inject + protected VolumeDao volumeDao; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + @Inject + protected HostDao hostDao; + @Inject + protected ClusterDao clusterDao; + @Inject + protected DiskOfferingDao diskOfferingDao; + + private List storagePoolAllocators; + + @Inject + public void setStoragePoolAllocators(final List storagePoolAllocators) { + this.storagePoolAllocators = storagePoolAllocators; + } + + public List getStoragePoolAllocators() { + return storagePoolAllocators; + } + + Pair findClusterAndHostIdForVmFromVolumes(long vmId) { + Long clusterId = null; + Long hostId = null; + List volumes = volumeDao.findByInstance(vmId); + for (VolumeVO volume : volumes) { + if (Volume.State.Ready.equals(volume.getState()) && + volume.getPoolId() != null) { + StoragePoolVO pool = storagePoolDao.findById(volume.getPoolId()); + if (pool != null && pool.getClusterId() != null) { + clusterId = pool.getClusterId(); + // hostId to be used only for sending commands, capacity check skipped + List hosts = hostDao.findHypervisorHostInCluster(pool.getClusterId()); + if (CollectionUtils.isNotEmpty(hosts)) { + hostId = hosts.get(0).getId(); + break; + } + } + } + } + return new Pair<>(clusterId, hostId); + } + + @Override + public Pair findClusterAndHostIdForVm(VirtualMachine vm, boolean skipCurrentHostForStartingVm) { + Long hostId = null; + Host host = null; + if (!skipCurrentHostForStartingVm || !State.Starting.equals(vm.getState())) { + hostId = vm.getHostId(); + } + Long clusterId = null; + if (hostId == null) { + if (vm.getLastHostId() == null) { + return findClusterAndHostIdForVmFromVolumes(vm.getId()); + } + hostId = vm.getLastHostId(); + host = hostDao.findById(hostId); + logger.debug("host id is null, using last host {} with id {}", host, hostId); + } + host = host == null ? hostDao.findById(hostId) : host; + if (host != null) { + clusterId = host.getClusterId(); + return new Pair<>(clusterId, hostId); + } + return findClusterAndHostIdForVmFromVolumes(vm.getId()); + } + + Pair findClusterAndHostIdForVm(VirtualMachine vm) { + return findClusterAndHostIdForVm(vm, false); + } + + @Override + public Pair findClusterAndHostIdForVm(long vmId) { + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm == null) { + return new Pair<>(null, null); + } + return findClusterAndHostIdForVm(vm); + } + + @Override + public boolean isDiskOfferingSuitableForVm(VMInstanceVO vm, VirtualMachineProfile profile, long podId, long clusterId, long hostId, long diskOfferingId) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(diskOfferingId); + VolumeVO dummyVolume = new VolumeVO("Data", vm.getDataCenterId(), podId, vm.getAccountId(), + vm.getDomainId(), vm.getId(), null, null, diskOffering.getProvisioningType(), diskOffering.getDiskSize(), Volume.Type.DATADISK); + try { + Field idField = dummyVolume.getClass().getDeclaredField("id"); + idField.setAccessible(true); + idField.set(dummyVolume, Volume.DISK_OFFERING_SUITABILITY_CHECK_VOLUME_ID); + } catch (NoSuchFieldException | IllegalAccessException ignored) { + return false; + } + dummyVolume.setDiskOfferingId(diskOfferingId); + DiskProfile diskProfile = new DiskProfile(dummyVolume, diskOffering, profile.getHypervisorType()); + diskProfile.setMinIops(diskOffering.getMinIops()); + diskProfile.setMaxIops(diskOffering.getMaxIops()); + ExcludeList avoid = new ExcludeList(); + DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), podId, clusterId, hostId, null, null); + for (StoragePoolAllocator allocator : storagePoolAllocators) { + List poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, 1); + if (CollectionUtils.isNotEmpty(poolListFromAllocator)) { + logger.debug("Found a suitable pool: {} for disk offering: {}", poolListFromAllocator.get(0).getName(), diskOffering.getName()); + return true; + } + } + return false; + } + + @Override + public Map getDiskOfferingSuitabilityForVm(long vmId, List diskOfferingIds) { + VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_VM) != null) { + return new HashMap<>(); + } + VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + Pair clusterAndHost = findClusterAndHostIdForVm(vm, false); + Long clusterId = clusterAndHost.first(); + Cluster cluster = clusterDao.findById(clusterId); + Map result = new HashMap<>(); + for (Long diskOfferingId : diskOfferingIds) { + result.put(diskOfferingId, isDiskOfferingSuitableForVm(vm, profile, cluster.getPodId(), clusterId, clusterAndHost.second(), diskOfferingId)); + } + return result; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeCommandService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeCommandService.java new file mode 100644 index 000000000000..aad3ae3a6724 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeCommandService.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; + +import com.cloud.agent.api.Command; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; + +public interface VmExpungeCommandService { + + void sendVolumeExpungeCommands(List volumeExpungeCommands, Long hostId, VMInstanceVO vm) + throws OperationTimedoutException, AgentUnavailableException; + + void sendFinalizeExpungeCommands(List finalizeExpungeCommands, List nicExpungeCommands, + VMInstanceVO vm, Long hostId) throws OperationTimedoutException, AgentUnavailableException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeCommandServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeCommandServiceImpl.java new file mode 100644 index 000000000000..f55d4cdc7e55 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeCommandServiceImpl.java @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.manager.Commands; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +public class VmExpungeCommandServiceImpl implements VmExpungeCommandService { + + private static final Logger logger = LogManager.getLogger(VmExpungeCommandServiceImpl.class); + + @Inject + protected AgentManager agentMgr; + + @Override + public void sendVolumeExpungeCommands(List volumeExpungeCommands, Long hostId, VMInstanceVO vm) + throws OperationTimedoutException, AgentUnavailableException { + if (CollectionUtils.isEmpty(volumeExpungeCommands) || hostId == null) { + return; + } + + final Commands cmds = new Commands(Command.OnError.Stop); + for (final Command volumeExpungeCommand : volumeExpungeCommands) { + volumeExpungeCommand.setBypassHostMaintenance(isValidSystemVMType(vm)); + cmds.addCommand(volumeExpungeCommand); + } + + agentMgr.send(hostId, cmds); + handleUnsuccessfulCommands(cmds, vm); + } + + @Override + public void sendFinalizeExpungeCommands(List finalizeExpungeCommands, List nicExpungeCommands, + VMInstanceVO vm, Long hostId) throws OperationTimedoutException, AgentUnavailableException { + if ((CollectionUtils.isEmpty(finalizeExpungeCommands) && CollectionUtils.isEmpty(nicExpungeCommands)) || hostId == null) { + return; + } + + final Commands cmds = new Commands(Command.OnError.Stop); + addAllExpungeCommandsFromList(finalizeExpungeCommands, cmds, vm); + addAllExpungeCommandsFromList(nicExpungeCommands, cmds, vm); + agentMgr.send(hostId, cmds); + if (!cmds.isSuccessful()) { + for (final Answer answer : cmds.getAnswers()) { + if (!answer.getResult()) { + logger.warn("Failed to expunge vm due to: {}", answer.getDetails()); + throw new CloudRuntimeException(String.format("Unable to expunge %s due to %s", vm, answer.getDetails())); + } + } + } + } + + protected void handleUnsuccessfulCommands(Commands cmds, VMInstanceVO vm) throws CloudRuntimeException { + String cmdsStr = cmds.toString(); + String vmToString = vm.toString(); + + if (cmds.isSuccessful()) { + logger.debug("The commands [{}] to {} were successful.", cmdsStr, vmToString); + return; + } + + logger.info("The commands [{}] to {} were unsuccessful. Handling answers.", cmdsStr, vmToString); + + Answer[] answers = cmds.getAnswers(); + if (answers == null) { + logger.debug("There are no answers to commands [{}] to {}.", cmdsStr, vmToString); + return; + } + + for (Answer answer : answers) { + String details = answer.getDetails(); + if (!answer.getResult()) { + String message = String.format("Unable to expunge %s due to [%s].", vmToString, details); + logger.error(message); + throw new CloudRuntimeException(message); + } + + logger.debug("Commands [{}] to {} got answer [{}].", cmdsStr, vmToString, details); + } + } + + private void addAllExpungeCommandsFromList(List cmdList, Commands cmds, VMInstanceVO vm) { + if (CollectionUtils.isEmpty(cmdList)) { + return; + } + for (final Command command : cmdList) { + command.setBypassHostMaintenance(isValidSystemVMType(vm)); + logger.trace("Adding expunge command [{}] for VM [{}]", command.toString(), vm.toString()); + cmds.addCommand(command); + } + } + + private boolean isValidSystemVMType(VirtualMachine vm) { + return VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType()) || + VirtualMachine.Type.ConsoleProxy.equals(vm.getType()); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeOrchestrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeOrchestrationService.java new file mode 100644 index 000000000000..1e85a35af6bd --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeOrchestrationService.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; + +public interface VmExpungeOrchestrationService { + + void expunge(String vmUuid) throws ResourceUnavailableException; + + void advanceExpunge(String vmUuid) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException; + + void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException; + + boolean isVmDestroyed(VMInstanceVO vm); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeOrchestrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeOrchestrationServiceImpl.java new file mode 100644 index 000000000000..5799f24cb018 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmExpungeOrchestrationServiceImpl.java @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.resource.ResourceCleanupService; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.Command; +import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmExpungeOrchestrationServiceImpl implements VmExpungeOrchestrationService { + + private static final Logger logger = LogManager.getLogger(VmExpungeOrchestrationServiceImpl.class); + + @Inject + protected VMInstanceDao vmDao; + @Inject + protected UserVmDao userVmDao; + @Inject + protected HypervisorGuruManager hvGuruMgr; + @Inject + protected NetworkOrchestrationService networkMgr; + @Inject + protected VolumeOrchestrationService volumeMgr; + @Inject + protected VmExpungeCommandService vmExpungeCommandService; + @Inject + protected UserVmDeployAsIsDetailsDao userVmDeployAsIsDetailsDao; + @Inject + protected AnnotationDao annotationDao; + @Inject + protected ResourceCleanupService resourceCleanupService; + @Inject + protected VmIscsiTargetManager vmIscsiTargetManager; + @Inject + @Lazy + protected VirtualMachineManager virtualMachineManager; + @Inject + @Lazy + protected VmStateMachineActions vmStateMachineActions; + + @Override + public void expunge(final String vmUuid) throws ResourceUnavailableException { + try { + advanceExpunge(vmUuid); + } catch (final OperationTimedoutException e) { + throw new CloudRuntimeException("Operation timed out", e); + } catch (final ConcurrentOperationException e) { + throw new CloudRuntimeException("Concurrent operation ", e); + } + } + + @Override + public void advanceExpunge(final String vmUuid) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException { + final VMInstanceVO vm = vmDao.findByUuid(vmUuid); + advanceExpunge(vm); + } + + @Override + public boolean isVmDestroyed(VMInstanceVO vm) { + if (vm == null || vm.getRemoved() != null) { + logger.debug("Unable to find vm or vm is expunged: {}", vm); + return true; + } + return false; + } + + @Override + public void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableException, OperationTimedoutException, ConcurrentOperationException { + if (isVmDestroyed(vm)) { + return; + } + + if (HypervisorType.External.equals(vm.getHypervisorType())) { + UserVmVO userVM = userVmDao.findById(vm.getId()); + userVmDao.loadDetails(userVM); + userVM.setDetail(VmDetailConstants.EXPUNGE_EXTERNAL_VM, Boolean.TRUE.toString()); + userVmDao.saveDetails(userVM); + } + + virtualMachineManager.advanceStop(vm.getUuid(), VirtualMachineManagerImpl.VmDestroyForcestop.value()); + vm = vmDao.findByUuid(vm.getUuid()); + + try { + if (!vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, vm.getHostId())) { + logger.debug("Unable to expunge the vm because it is not in the correct state: {}", vm); + throw new CloudRuntimeException("Unable to expunge " + vm); + } + } catch (final NoTransitionException e) { + logger.debug("Unable to expunge the vm because it is not in the correct state: {}", vm); + throw new CloudRuntimeException("Unable to expunge " + vm, e); + } + + logger.debug("Expunging vm {}", vm); + + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(vm.getHypervisorType()); + + List vmNics = profile.getNics(); + logger.debug("Cleaning up NICS [{}] of {}.", vmNics.stream().map(nic -> nic.toString()).collect(Collectors.joining(", ")), vm.toString()); + final List nicExpungeCommands = hvGuru.finalizeExpungeNics(vm, profile.getNics()); + networkMgr.cleanupNics(profile); + + logger.debug("Cleaning up hypervisor data structures (ex. SRs in XenServer) for managed storage. Data from {}.", vm.toString()); + + final List volumeExpungeCommands = hvGuru.finalizeExpungeVolumes(vm); + final Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); + List> targets = getTargets(hostId, vm.getId()); + + vmExpungeCommandService.sendVolumeExpungeCommands(volumeExpungeCommands, hostId, vm); + + if (hostId != null) { + volumeMgr.revokeAccess(vm.getId(), hostId); + } + + volumeMgr.cleanupVolumes(vm.getId()); + + if (hostId != null && CollectionUtils.isNotEmpty(targets)) { + removeDynamicTargets(hostId, targets); + } + + final VirtualMachineGuru guru = vmStateMachineActions.getVmGuru(vm); + guru.finalizeExpunge(vm); + + userVmDeployAsIsDetailsDao.removeDetails(vm.getId()); + annotationDao.removeByEntityType(AnnotationService.EntityType.VM.name(), vm.getUuid()); + + final List finalizeExpungeCommands = hvGuru.finalizeExpunge(vm); + vmExpungeCommandService.sendFinalizeExpungeCommands(finalizeExpungeCommands, nicExpungeCommands, vm, hostId); + + logger.debug("Expunged {}", vm); + resourceCleanupService.purgeExpungedVmResourcesLaterIfNeeded(vm); + } + + private List> getTargets(Long hostId, long vmId) { + return vmIscsiTargetManager.getTargets(hostId, vmId); + } + + private void removeDynamicTargets(long hostId, List> targets) { + vmIscsiTargetManager.removeDynamicTargets(hostId, targets); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmExternalProvisioningManager.java b/engine/orchestration/src/main/java/com/cloud/vm/VmExternalProvisioningManager.java new file mode 100644 index 000000000000..6ef41422f789 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmExternalProvisioningManager.java @@ -0,0 +1,133 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.dc.DataCenter; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.exception.CloudRuntimeException; + +/** + * External-hypervisor provisioning handshake and command-decoration helpers. + * + *

Handles the {@code PrepareExternalProvisioning} command/answer round-trip + * for External hypervisor VMs (pre-start metadata exchange, NIC/detail updates), + * and decorates {@link StartCommand}, {@link StopCommand}, and + * {@link RebootCommand} with the per-host external-access details required by + * the External hypervisor driver. + * + *

Extracted from {@link VirtualMachineManagerImpl} as part of the + * Phase 4 Spring-component decomposition. + */ +public interface VmExternalProvisioningManager { + + /** + * Set the {@code metadataManufacturer} and {@code metadataProductName} + * fields on {@code vmTO} from the zone-scoped config keys, falling back + * to global defaults when the zone-level value is blank. + */ + void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMInstanceVO vm); + + /** + * Persist new VM details returned by a PrepareExternalProvisioning answer + * back to {@code vmTO} and the {@link UserVmVO} detail table. No-ops when + * {@code newDetails} is {@code null} or equal to the details already on + * {@code vmTO}. + */ + void updateExternalVmDetailsFromPrepareAnswer(VirtualMachineTO vmTO, UserVmVO userVmVO, + Map newDetails); + + /** + * Apply VNC-password and detail updates from {@code updatedTO} back into + * {@code vmTO} and the underlying {@link UserVmVO} row. No-ops when + * neither field changed. + */ + void updateExternalVmDataFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO); + + /** + * Reconcile NIC MAC/IP addresses returned by a PrepareExternalProvisioning + * answer against the live {@link com.cloud.vm.dao.NicDao} rows. No-ops + * when either NIC array is {@code null}. + */ + void updateExternalVmNicsFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO); + + /** + * Convenience wrapper: apply both data and NIC updates from a + * PrepareExternalProvisioning answer. No-ops when {@code updatedTO} + * is {@code null}. + */ + void updateExternalVmFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO); + + /** + * Send a {@code PrepareExternalProvisioningCommand} to the host for the + * first start of an External VM whose template's extension requires it, + * then reconcile any updates returned in the answer. + * + *

The caller is responsible for pre-computing {@code vmTO} (via + * {@code toVmTO(vmProfile)}) so that the {@code @Spy} stub in unit tests + * for {@link VirtualMachineManagerImpl} is honoured correctly. The NIC + * array on {@code vmTO} will be populated from the database when it is + * empty. + * + * @param firstStart {@code true} only on the very first start of the VM + * @param host the destination host; may be {@code null} + * @param vmProfile VM profile including NICs + * @param dataCenter target zone (used to resolve NIC profiles) + * @param vmTO pre-built transfer object for the VM + * @throws CloudRuntimeException when the agent call fails or returns an + * unexpected/negative answer + */ + void processPrepareExternalProvisioning(boolean firstStart, Host host, + VirtualMachineProfile vmProfile, DataCenter dataCenter, VirtualMachineTO vmTO) + throws CloudRuntimeException; + + /** + * Populate a {@link StartCommand} with the per-host external-access + * details and the default-NIC VLAN segment name. No-ops for non-External + * hypervisors. + */ + void updateStartCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, StartCommand command); + + /** + * Populate a {@link StopCommand} with the per-host external-access + * details and a cleaned-up {@link VirtualMachineTO}. No-ops for + * non-External hypervisors or when the VM profile has no host id. + * + *

The caller is responsible for pre-computing {@code vmTO} (via + * {@code ObjectUtils.defaultIfNull(stopCommand.getVirtualMachine(), toVmTO(vmProfile))}) + * so that the {@code @Spy} stub in unit tests is honoured correctly. + * + * @param hypervisorType the VM's hypervisor type + * @param vmProfile VM profile including the host id + * @param stopCommand command to decorate + * @param vmTO pre-built (or pre-resolved from command) transfer object + */ + void updateStopCommandForExternalHypervisorType(HypervisorType hypervisorType, + VirtualMachineProfile vmProfile, StopCommand stopCommand, VirtualMachineTO vmTO); + + /** + * Populate a {@link RebootCommand} with the per-host external-access + * details. No-ops for non-External hypervisors. + */ + void updateRebootCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, RebootCommand rebootCmd); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmExternalProvisioningManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmExternalProvisioningManagerImpl.java new file mode 100644 index 000000000000..8696cad2ce5b --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmExternalProvisioningManagerImpl.java @@ -0,0 +1,289 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao; +import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.dc.DataCenter; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; +import com.cloud.utils.StringUtils; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; + +/** + * External-hypervisor provisioning handshake and command-decoration — extracted + * from {@link VirtualMachineManagerImpl}. + * + * @see VmExternalProvisioningManager + */ +@Component +public class VmExternalProvisioningManagerImpl implements VmExternalProvisioningManager { + + private static final Logger logger = LogManager.getLogger(VmExternalProvisioningManagerImpl.class); + + @Inject + private AgentManager agentMgr; + @Inject + private NicDao nicsDao; + @Inject + private UserVmDao userVmDao; + @Inject + private ExtensionsManager extensionsManager; + @Inject + private ExtensionDetailsDao extensionDetailsDao; + @Inject + private NetworkService networkService; + @Inject + private HostDao hostDao; + @Inject + private NetworkModel networkModel; + @Inject + private HypervisorGuruManager hvGuruMgr; + + @Override + public void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMInstanceVO vm) { + String metadataManufacturer = VirtualMachineManager.VmMetadataManufacturer.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataManufacturer)) { + metadataManufacturer = VirtualMachineManager.VmMetadataManufacturer.defaultValue(); + } + vmTO.setMetadataManufacturer(metadataManufacturer); + String metadataProduct = VirtualMachineManager.VmMetadataProductName.valueIn(vm.getDataCenterId()); + if (StringUtils.isBlank(metadataProduct)) { + metadataProduct = String.format("CloudStack %s Hypervisor", vm.getHypervisorType().toString()); + } + vmTO.setMetadataProductName(metadataProduct); + } + + @Override + public void updateExternalVmDetailsFromPrepareAnswer(VirtualMachineTO vmTO, UserVmVO userVmVO, + Map newDetails) { + if (newDetails == null || newDetails.equals(vmTO.getDetails())) { + return; + } + vmTO.setDetails(newDetails); + userVmVO.setDetails(newDetails); + userVmDao.saveDetails(userVmVO); + } + + @Override + public void updateExternalVmDataFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { + final String vncPassword = updatedTO.getVncPassword(); + final Map details = updatedTO.getDetails(); + if ((vncPassword == null || vncPassword.equals(vmTO.getVncPassword())) && + (details == null || details.equals(vmTO.getDetails()))) { + return; + } + UserVmVO userVmVO = userVmDao.findById(vmTO.getId()); + if (userVmVO == null) { + return; + } + if (vncPassword != null && !vncPassword.equals(userVmVO.getPassword())) { + userVmVO.setVncPassword(vncPassword); + vmTO.setVncPassword(vncPassword); + } + updateExternalVmDetailsFromPrepareAnswer(vmTO, userVmVO, updatedTO.getDetails()); + } + + @Override + public void updateExternalVmNicsFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { + if (ObjectUtils.anyNull(vmTO.getNics(), updatedTO.getNics())) { + return; + } + Map originalNicsByUuid = new HashMap<>(); + for (NicTO nic : vmTO.getNics()) { + originalNicsByUuid.put(nic.getNicUuid(), nic); + } + for (NicTO updatedNicTO : updatedTO.getNics()) { + final String nicUuid = updatedNicTO.getNicUuid(); + NicTO originalNicTO = originalNicsByUuid.get(nicUuid); + if (originalNicTO == null) { + continue; + } + final String mac = updatedNicTO.getMac(); + final String ip4 = updatedNicTO.getIp(); + final String ip6 = updatedNicTO.getIp6Address(); + if (Objects.equals(mac, originalNicTO.getMac()) && + Objects.equals(ip4, originalNicTO.getIp()) && + Objects.equals(ip6, originalNicTO.getIp6Address())) { + continue; + } + NicVO nicVO = nicsDao.findByUuid(nicUuid); + if (nicVO == null) { + continue; + } + logger.debug("Updating {} during External VM preparation", nicVO); + if (ip4 != null && !ip4.equals(nicVO.getIPv4Address())) { + nicVO.setIPv4Address(ip4); + originalNicTO.setIp(ip4); + } + if (ip6 != null && !ip6.equals(nicVO.getIPv6Address())) { + nicVO.setIPv6Address(ip6); + originalNicTO.setIp6Address(ip6); + } + if (mac != null && !mac.equals(nicVO.getMacAddress())) { + nicVO.setMacAddress(mac); + originalNicTO.setMac(mac); + } + nicsDao.update(nicVO.getId(), nicVO); + } + } + + @Override + public void updateExternalVmFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { + if (updatedTO == null) { + return; + } + updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); + updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + } + + @Override + public void processPrepareExternalProvisioning(boolean firstStart, Host host, + VirtualMachineProfile vmProfile, DataCenter dataCenter, VirtualMachineTO virtualMachineTO) + throws CloudRuntimeException { + if (virtualMachineTO.getNics() == null || virtualMachineTO.getNics().length == 0) { + List nics = nicsDao.listByVmId(vmProfile.getId()); + NicTO[] nicTOs = new NicTO[nics.size()]; + nics.forEach(nicVO -> { + NicTO nicTO = toNicTO(networkModel.getNicProfile(vmProfile.getVirtualMachine(), nicVO, dataCenter), + host.getHypervisorType()); + nicTOs[nicTO.getDeviceId()] = nicTO; + }); + virtualMachineTO.setNics(nicTOs); + } + Map vmDetails = virtualMachineTO.getExternalDetails(); + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, + vmDetails); + PrepareExternalProvisioningCommand cmd = new PrepareExternalProvisioningCommand(virtualMachineTO); + cmd.setExternalDetails(externalDetails); + Answer answer = null; + CloudRuntimeException cre = new CloudRuntimeException("Failed to prepare VM"); + try { + answer = agentMgr.send(host.getId(), cmd); + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.error("Failed PrepareExternalProvisioningCommand due to : {}", e.getMessage(), e); + throw cre; + } + if (answer == null) { + logger.error("Invalid answer received for PrepareExternalProvisioningCommand"); + throw cre; + } + if (!(answer instanceof PrepareExternalProvisioningAnswer)) { + logger.error("Unexpected answer received for PrepareExternalProvisioningCommand: [result: {}, details: {}]", + answer.getResult(), answer.getDetails()); + throw cre; + } + PrepareExternalProvisioningAnswer prepareAnswer = (PrepareExternalProvisioningAnswer) answer; + if (!prepareAnswer.getResult()) { + logger.error("Unexpected answer received for PrepareExternalProvisioningCommand: [result: {}, details: {}]", + answer.getResult(), answer.getDetails()); + throw cre; + } + updateExternalVmFromPrepareAnswer(virtualMachineTO, prepareAnswer.getVirtualMachineTO()); + } + + @Override + public void updateStartCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, StartCommand command) { + if (!HypervisorType.External.equals(host.getHypervisorType())) { + return; + } + Map vmExternalDetails = vmTO.getExternalDetails(); + for (NicTO nic : vmTO.getNics()) { + if (!nic.isDefaultNic()) { + continue; + } + vmExternalDetails.put(VmDetailConstants.CLOUDSTACK_VLAN, networkService.getNicVlanValueForExternalVm(nic)); + } + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmExternalDetails); + command.setExternalDetails(externalDetails); + } + + @Override + public void updateStopCommandForExternalHypervisorType(HypervisorType hypervisorType, + VirtualMachineProfile vmProfile, StopCommand stopCommand, VirtualMachineTO vmTO) { + if (!HypervisorType.External.equals(hypervisorType) || vmProfile.getHostId() == null) { + return; + } + HostVO host = hostDao.findById(vmProfile.getHostId()); + if (host == null) { + return; + } + if (MapUtils.isEmpty(vmTO.getGuestOsDetails())) { + vmTO.setGuestOsDetails(null); + } + if (MapUtils.isEmpty(vmTO.getExtraConfig())) { + vmTO.setExtraConfig(null); + } + if (MapUtils.isEmpty(vmTO.getNetworkIdToNetworkNameMap())) { + vmTO.setNetworkIdToNetworkNameMap(null); + } + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmTO.getExternalDetails()); + stopCommand.setVirtualMachine(vmTO); + stopCommand.setExternalDetails(externalDetails); + } + + @Override + public void updateRebootCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, RebootCommand rebootCmd) { + if (!HypervisorType.External.equals(host.getHypervisorType())) { + return; + } + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmTO.getExternalDetails()); + rebootCmd.setExternalDetails(externalDetails); + } + + /** + * Build a {@link NicTO} from the given profile for the specified hypervisor, + * delegating to the hypervisor guru — mirrors + * {@link VirtualMachineManagerImpl#toNicTO(NicProfile, HypervisorType)}. + */ + protected NicTO toNicTO(NicProfile nicProfile, HypervisorType hypervisorType) { + HypervisorGuru hvGuru = hvGuruMgr.getGuru(hypervisorType); + return hvGuru.toNicTO(nicProfile); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmIscsiTargetManager.java b/engine/orchestration/src/main/java/com/cloud/vm/VmIscsiTargetManager.java new file mode 100644 index 000000000000..f139b49f2387 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmIscsiTargetManager.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.List; +import java.util.Map; + +/** + * VMware managed-iSCSI dynamic-target cleanup helpers — gather the + * dynamic iSCSI targets a VM's managed volumes contribute to a VMware + * host, then ask that host (and its cluster siblings) to drop them from + * the iSCSI HBA's dynamic-target list. + * + *

Only VMware hosts produce non-empty target lists; every other + * hypervisor short-circuits to an empty result, leaving the + * {@link com.cloud.agent.api.ModifyTargetsCommand} unsent. + * + *

Extracted from {@link VirtualMachineManagerImpl} as part of the + * Phase 4 Spring-component decomposition. + */ +public interface VmIscsiTargetManager { + + /** + * Collect the host/port/IQN tuples for every managed primary storage + * pool backing the VM's volumes. Returns an empty list when the + * host is not VMware, the host record is missing, the VM has no + * volumes, or none of its pools are flagged as managed. + */ + List> getTargets(Long hostId, long vmId); + + /** + * Send a {@link com.cloud.agent.api.ModifyTargetsCommand} that + * removes the supplied {@code targets} from the dynamic-target list + * on {@code hostId} and every other host in its cluster. + */ + void removeDynamicTargets(long hostId, List> targets); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmIscsiTargetManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmIscsiTargetManagerImpl.java new file mode 100644 index 000000000000..e7d3b72446c6 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmIscsiTargetManagerImpl.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ModifyTargetsCommand; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; + +/** + * VMware managed-iSCSI dynamic-target cleanup — extracted from + * {@link VirtualMachineManagerImpl}. + * + * @see VmIscsiTargetManager + */ +@Component +public class VmIscsiTargetManagerImpl implements VmIscsiTargetManager { + + private static final Logger logger = LogManager.getLogger(VmIscsiTargetManagerImpl.class); + + @Inject + private HostDao hostDao; + @Inject + private VolumeDao volumeDao; + @Inject + private PrimaryDataStoreDao storagePoolDao; + @Inject + private AgentManager agentMgr; + + @Override + public List> getTargets(Long hostId, long vmId) { + List> targets = new ArrayList<>(); + + HostVO hostVO = hostDao.findById(hostId); + + if (hostVO == null || hostVO.getHypervisorType() != HypervisorType.VMware) { + return targets; + } + + List volumes = volumeDao.findByInstance(vmId); + + if (CollectionUtils.isEmpty(volumes)) { + return targets; + } + + for (VolumeVO volume : volumes) { + StoragePoolVO storagePoolVO = storagePoolDao.findById(volume.getPoolId()); + + if (storagePoolVO != null && storagePoolVO.isManaged()) { + Map target = new HashMap<>(); + + target.put(ModifyTargetsCommand.STORAGE_HOST, storagePoolVO.getHostAddress()); + target.put(ModifyTargetsCommand.STORAGE_PORT, String.valueOf(storagePoolVO.getPort())); + target.put(ModifyTargetsCommand.IQN, volume.get_iScsiName()); + + targets.add(target); + } + } + + return targets; + } + + @Override + public void removeDynamicTargets(long hostId, List> targets) { + ModifyTargetsCommand cmd = new ModifyTargetsCommand(); + + cmd.setTargets(targets); + cmd.setApplyToAllHostsInCluster(true); + cmd.setAdd(false); + cmd.setTargetTypeToRemove(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC); + + sendModifyTargetsCommand(cmd, hostId); + } + + protected void sendModifyTargetsCommand(ModifyTargetsCommand cmd, long hostId) { + Answer answer = agentMgr.easySend(hostId, cmd); + + if (answer == null) { + logger.warn("Unable to get an answer to the modify targets command. Targets [{}].", + () -> cmd.getTargets().stream().map(target -> target.toString()).collect(Collectors.joining(", "))); + return; + } + + if (!answer.getResult()) { + logger.warn("Unable to modify targets [{}] on the host [{}].", + () -> cmd.getTargets().stream().map(target -> target.toString()).collect(Collectors.joining(", ")), () -> hostId); + } + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmMetadataSyncService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmMetadataSyncService.java new file mode 100644 index 000000000000..9f95bb03443b --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmMetadataSyncService.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +public interface VmMetadataSyncService { + + void syncVMMetaData(Map vmMetadatum); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmMetadataSyncServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmMetadataSyncServiceImpl.java new file mode 100644 index 000000000000..78c596728f56 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmMetadataSyncServiceImpl.java @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.utils.Pair; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmMetadataSyncServiceImpl implements VmMetadataSyncService { + + @Inject + protected UserVmDao userVmDao; + @Inject + protected VMInstanceDao vmDao; + + @Override + public void syncVMMetaData(final Map vmMetadatum) { + if (vmMetadatum == null || vmMetadatum.isEmpty()) { + return; + } + List, Pair>> vmDetails = userVmDao.getVmsDetailByNames(vmMetadatum.keySet(), "platform"); + for (final Map.Entry entry : vmMetadatum.entrySet()) { + final String name = entry.getKey(); + final String platform = entry.getValue(); + if (platform == null || platform.isEmpty()) { + continue; + } + + boolean found = false; + for(Pair, Pair> vmDetail : vmDetails ) { + Pair vmNameTypePair = vmDetail.first(); + if(vmNameTypePair.first().equals(name)) { + found = true; + if(vmNameTypePair.second() == VirtualMachine.Type.User) { + Pair detailPair = vmDetail.second(); + String platformDetail = detailPair.second(); + + if (platformDetail != null && platformDetail.equals(platform)) { + break; + } + updateVmMetaData(detailPair.first(), platform); + } + break; + } + } + + if(!found) { + VMInstanceVO vm = vmDao.findVMByInstanceName(name); + if(vm != null && vm.getType() == VirtualMachine.Type.User) { + updateVmMetaData(vm.getId(), platform); + } + } + } + } + + private void updateVmMetaData(Long vmId, String platform) { + UserVmVO userVm = userVmDao.findById(vmId); + userVmDao.loadDetails(userVm); + if ( userVm.details.containsKey(VmDetailConstants.TIME_OFFSET)) { + userVm.details.remove(VmDetailConstants.TIME_OFFSET); + } + userVm.setDetail(VmDetailConstants.PLATFORM, platform); + String pvdriver = "xenserver56"; + if ( platform.contains("device_id")) { + pvdriver = "xenserver61"; + } + if (!userVm.details.containsKey(VmDetailConstants.HYPERVISOR_TOOLS_VERSION) || !userVm.details.get(VmDetailConstants.HYPERVISOR_TOOLS_VERSION).equals(pvdriver)) { + userVm.setDetail(VmDetailConstants.HYPERVISOR_TOOLS_VERSION, pvdriver); + } + userVmDao.saveDetails(userVm); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmMigrateAwayPlanningService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrateAwayPlanningService.java new file mode 100644 index 000000000000..b1b0ee257969 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrateAwayPlanningService.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.host.Host; + +public interface VmMigrateAwayPlanningService { + + void migrateAway(String vmUuid, long srcHostId) throws InsufficientServerCapacityException; + + void orchestrateMigrateAway(String vmUuid, long srcHostId, DeploymentPlanner planner) throws InsufficientServerCapacityException; + + boolean checkIfVmHasClusterWideVolumes(Long vmId); + + DataCenterDeployment getMigrationDeployment(VirtualMachine vm, Host host, Long poolId, ExcludeList excludes); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmMigrateAwayPlanningServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrateAwayPlanningServiceImpl.java new file mode 100644 index 000000000000..be6b4c37ee46 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrateAwayPlanningServiceImpl.java @@ -0,0 +1,220 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.exception.AffinityConflictException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.ScopeType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmMigrateAwayPlanningServiceImpl implements VmMigrateAwayPlanningService { + + private static final Logger logger = LogManager.getLogger(VmMigrateAwayPlanningServiceImpl.class); + + @Inject + protected VMInstanceDao vmDao; + @Inject + protected ServiceOfferingDao offeringDao; + @Inject + protected HostDao hostDao; + @Inject + protected VolumeDao volsDao; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + @Inject + protected ClusterDao clusterDao; + @Inject + protected DeploymentPlanningManager dpMgr; + @Inject + protected HighAvailabilityManager haMgr; + @Inject + protected VmWorkJobQueueService vmWorkJobQueueService; + @Inject + @Lazy + protected VirtualMachineManagerImpl virtualMachineManager; + + @Override + public void migrateAway(final String vmUuid, final long srcHostId) throws InsufficientServerCapacityException { + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + final VirtualMachine vm = vmDao.findByUuid(vmUuid); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + try { + orchestrateMigrateAway(vmUuid, srcHostId, null); + } catch (final InsufficientServerCapacityException e) { + logger.warn("Failed to deploy vm {} with original planner, sending HAPlanner", vmUuid); + orchestrateMigrateAway(vmUuid, srcHostId, haMgr.getHAPlanner()); + } + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); + } + } else { + final Outcome outcome = vmWorkJobQueueService.migrateVmAwayThroughJobQueue(vmUuid, srcHostId); + + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmAway"); + + try { + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + throw new RuntimeException("Unexpected exception", ex); + } + } + } + + @Override + public void orchestrateMigrateAway(final String vmUuid, final long srcHostId, final DeploymentPlanner planner) throws InsufficientServerCapacityException { + final VMInstanceVO vm = vmDao.findByUuid(vmUuid); + if (vm == null) { + String message = String.format("Unable to find VM with uuid [%s].", vmUuid); + logger.warn(message); + throw new CloudRuntimeException(message); + } + + ServiceOfferingVO offeringVO = offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offeringVO, null, null); + + final Long hostId = vm.getHostId(); + if (hostId == null) { + String message = String.format("Unable to migrate %s due to it does not have a host id.", vm.toString()); + logger.warn(message); + throw new CloudRuntimeException(message); + } + + final Host host = hostDao.findById(hostId); + Long poolId = null; + final List vols = volsDao.findReadyRootVolumesByInstance(vm.getId()); + for (final VolumeVO rootVolumeOfVm : vols) { + final StoragePoolVO rootDiskPool = storagePoolDao.findById(rootVolumeOfVm.getPoolId()); + if (rootDiskPool != null) { + poolId = rootDiskPool.getId(); + } + } + + final ExcludeList excludes = new ExcludeList(); + excludes.addHost(hostId); + DataCenterDeployment plan = getMigrationDeployment(vm, host, poolId, excludes); + + DeployDestination dest = null; + while (true) { + + try { + plan.setMigrationPlan(true); + dest = dpMgr.planDeployment(profile, plan, excludes, planner); + } catch (final AffinityConflictException e2) { + String message = String.format("Unable to create deployment, affinity rules associated to the %s conflict.", vm.toString()); + logger.warn(message, e2); + throw new CloudRuntimeException(message, e2); + } + if (dest == null) { + logger.warn("Unable to find destination for migrating the vm {}", profile); + throw new InsufficientServerCapacityException("Unable to find a server to migrate to.", DataCenter.class, host.getDataCenterId()); + } + logger.debug("Found destination {} for migrating to.", dest); + + excludes.addHost(dest.getHost().getId()); + try { + virtualMachineManager.migrate(vm, srcHostId, dest); + return; + } catch (ResourceUnavailableException | ConcurrentOperationException e) { + logger.warn("Unable to migrate {} to {} due to [{}]", vm.toString(), dest.getHost().toString(), e.getMessage(), e); + } + + try { + virtualMachineManager.advanceStop(vmUuid, true); + throw new CloudRuntimeException("Unable to migrate " + vm); + } catch (final ResourceUnavailableException | ConcurrentOperationException | OperationTimedoutException e) { + logger.error("Unable to stop {} due to [{}].", vm.toString(), e.getMessage(), e); + throw new CloudRuntimeException("Unable to migrate " + vm); + } + } + } + + /** + * Check if the virtual machine has any volume in cluster-wide pool + * @param vmId id of the virtual machine + * @return true if volume exists on cluster-wide pool else false + */ + @Override + public boolean checkIfVmHasClusterWideVolumes(Long vmId) { + final List volumesList = volsDao.findCreatedByInstance(vmId); + + return volumesList.parallelStream() + .anyMatch(vol -> storagePoolDao.findById(vol.getPoolId()).getScope().equals(ScopeType.CLUSTER)); + + } + + @Override + public DataCenterDeployment getMigrationDeployment(final VirtualMachine vm, final Host host, final Long poolId, final ExcludeList excludes) { + if (MIGRATE_VM_ACROSS_CLUSTERS.valueIn(host.getDataCenterId()) && + (HypervisorType.VMware.equals(host.getHypervisorType()) || !checkIfVmHasClusterWideVolumes(vm.getId()))) { + logger.info("Searching for hosts in the zone for vm migration"); + List clustersToExclude = clusterDao.listAllClusterIds(host.getDataCenterId()); + List clusterList = clusterDao.listByDcHyType(host.getDataCenterId(), host.getHypervisorType().toString()); + for (ClusterVO cluster : clusterList) { + clustersToExclude.remove(cluster.getId()); + } + for (Long clusterId : clustersToExclude) { + excludes.addCluster(clusterId); + } + if (VirtualMachine.systemVMs.contains(vm.getType())) { + return new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), null, null, poolId, null); + } + return new DataCenterDeployment(host.getDataCenterId(), null, null, null, poolId, null); + } + return new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, poolId, null); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmMigrationCheckpointService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrationCheckpointService.java new file mode 100644 index 000000000000..31fed485999b --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrationCheckpointService.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; + +public interface VmMigrationCheckpointService { + + void endSnapshotChainForVolumes(Map volumeToPoolMap, HypervisorType hypervisorType); + + void recreateCheckpointsKvmOnVmAfterMigration(VMInstanceVO vm, long hostId); + + List getVmVolumesWithCheckpointsToRecreate(VMInstanceVO vm); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmMigrationCheckpointServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrationCheckpointServiceImpl.java new file mode 100644 index 000000000000..8a8f80e33d44 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmMigrationCheckpointServiceImpl.java @@ -0,0 +1,127 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.RecreateCheckpointsCommand; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.snapshot.SnapshotManager; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +public class VmMigrationCheckpointServiceImpl implements VmMigrationCheckpointService { + + private static final Logger logger = LogManager.getLogger(VmMigrationCheckpointServiceImpl.class); + + @Inject + private AgentManager agentManager; + @Inject + private VolumeOrchestrationService volumeOrchestrationService; + @Inject + private SnapshotManager snapshotManager; + @Inject + private VolumeDao volumeDao; + + @Override + public void endSnapshotChainForVolumes(Map volumeToPoolMap, HypervisorType hypervisorType) { + Set volumes = volumeToPoolMap.keySet(); + volumes.forEach(volume -> { + Volume volumeOnDestination = volumeDao.findByPoolIdName(volumeToPoolMap.get(volume).getId(), volume.getName()); + snapshotManager.endSnapshotChainForVolume(volumeOnDestination.getId(), hypervisorType); + }); + } + + @Override + public void recreateCheckpointsKvmOnVmAfterMigration(VMInstanceVO vm, long hostId) { + if (!HypervisorType.KVM.equals(vm.getHypervisorType())) { + logger.debug("Will not recreate checkpoint on VM as it is not running on KVM, thus it is not needed."); + return; + } + + List volumes = getVmVolumesWithCheckpointsToRecreate(vm); + + if (volumes.isEmpty()) { + logger.debug("Will not recreate checkpoints on VM as its volumes do not have any checkpoints associated with them."); + return; + } + + RecreateCheckpointsCommand recreateCheckpointsCommand = new RecreateCheckpointsCommand(volumes, vm.getInstanceName()); + Answer answer = null; + try { + logger.debug(String.format("Recreating the volume checkpoints with URLs [%s] of volumes [%s] on %s as part of the migration process.", + volumes.stream().map(VolumeObjectTO::getCheckpointPaths).collect(Collectors.toList()), volumes, vm)); + answer = agentManager.send(hostId, recreateCheckpointsCommand); + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.error(String.format("Exception while sending command to host [%s] to recreate checkpoints with URLs [%s] of volumes [%s] on %s due to: [%s].", + hostId, volumes.stream().map(VolumeObjectTO::getCheckpointPaths).collect(Collectors.toList()), volumes, vm, e.getMessage()), e); + throw new CloudRuntimeException(e); + } finally { + if (answer != null && answer.getResult()) { + logger.debug(String.format("Successfully recreated checkpoints on VM [%s].", vm)); + return; + } + + logger.debug(String.format("Migration on VM [%s] was successful; however, we weren't able to recreate the checkpoints on it. Marking the snapshot chain as ended." + + " Next snapshot will create a new snapshot chain.", vm)); + + volumes.forEach(volumeObjectTO -> snapshotManager.endSnapshotChainForVolume(volumeObjectTO.getId(), HypervisorType.KVM)); + } + } + + @Override + public List getVmVolumesWithCheckpointsToRecreate(VMInstanceVO vm) { + List vmVolumes = volumeDao.findByInstance(vm.getId()); + List volumes = new ArrayList<>(); + + for (VolumeVO volume : vmVolumes) { + Pair, Set> volumeCheckpointPathsAndImageStoreUrls = + volumeOrchestrationService.getVolumeCheckpointPathsAndImageStoreUrls(volume.getId(), HypervisorType.KVM); + if (volumeCheckpointPathsAndImageStoreUrls.first().isEmpty()) { + continue; + } + VolumeObjectTO volumeTo = new VolumeObjectTO(); + volumeTo.setCheckpointPaths(volumeCheckpointPathsAndImageStoreUrls.first()); + volumeTo.setCheckpointImageStoreUrls(volumeCheckpointPathsAndImageStoreUrls.second()); + volumeTo.setPath(volume.getPath()); + volumes.add(volumeTo); + } + return volumes; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkAttachmentOrchestrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkAttachmentOrchestrationService.java new file mode 100644 index 000000000000..55d010d5c8b2 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkAttachmentOrchestrationService.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.net.URI; + +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; + +public interface VmNetworkAttachmentOrchestrationService { + + NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile requested, BackendNicOperations backendNicOperations) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + + void checkIfNetworkExistsForUserVM(VirtualMachine virtualMachine, Network network); + + NicTO toNicTO(NicProfile nic, HypervisorType hypervisorType); + + boolean removeNicFromVm(VirtualMachine vm, Nic nic, BackendNicOperations backendNicOperations) + throws ConcurrentOperationException, ResourceUnavailableException; + + boolean removeVmFromNetwork(VirtualMachine vm, Network network, URI broadcastUri, BackendNicOperations backendNicOperations) + throws ConcurrentOperationException, ResourceUnavailableException; + + interface BackendNicOperations { + boolean plugNic(Network network, NicTO nic, VirtualMachineTO vm, ReservationContext context, DeployDestination dest) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; + + boolean unplugNic(Network network, NicTO nic, VirtualMachineTO vm, ReservationContext context, DeployDestination dest) + throws ConcurrentOperationException, ResourceUnavailableException; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkAttachmentOrchestrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkAttachmentOrchestrationServiceImpl.java new file mode 100644 index 000000000000..5dc4267a48d0 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkAttachmentOrchestrationServiceImpl.java @@ -0,0 +1,280 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.net.URI; +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.dc.DataCenter; +import com.cloud.deploy.DeployDestination; +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmNetworkAttachmentOrchestrationServiceImpl implements VmNetworkAttachmentOrchestrationService { + + private static final Logger logger = LogManager.getLogger(VmNetworkAttachmentOrchestrationServiceImpl.class); + + @Inject + protected UserVmManager userVmMgr; + @Inject + protected NetworkOrchestrationService networkMgr; + @Inject + protected NetworkModel networkModel; + @Inject + protected VMInstanceDao vmDao; + @Inject + protected NicDao nicsDao; + @Inject + protected HostDao hostDao; + @Inject + protected NetworkDao networkDao; + @Inject + protected HypervisorGuruManager hvGuruMgr; + @Inject + protected EntityManager entityMgr; + + @Override + public NicProfile addVmToNetwork(final VirtualMachine vm, final Network network, final NicProfile requested, + BackendNicOperations backendNicOperations) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + final CallContext cctx = CallContext.current(); + + checkIfNetworkExistsForUserVM(vm, network); + logger.debug("Adding Instance {} to Network {}; requested NIC profile {}", vm, network, requested); + final VMInstanceVO vmVO = vmDao.findById(vm.getId()); + final ReservationContext context = new ReservationContextImpl(null, null, cctx.getCallingUser(), cctx.getCallingAccount()); + + final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); + + final DataCenter dc = entityMgr.findById(DataCenter.class, network.getDataCenterId()); + final Host host = hostDao.findById(vm.getHostId()); + final DeployDestination dest = new DeployDestination(dc, null, null, host); + + if (vm.getState() == State.Running) { + final NicProfile nic = networkMgr.createNicForVm(network, requested, context, vmProfile, true); + + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); + final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); + + final NicTO nicTO = toNicTO(nic, vmProfile.getVirtualMachine().getHypervisorType()); + + logger.debug("Plugging NIC for Instance {} in Network {}", vm, network); + + boolean result = false; + try { + result = backendNicOperations.plugNic(network, nicTO, vmTO, context, dest); + if (result) { + userVmMgr.setupVmForPvlan(true, vm.getHostId(), nic); + logger.debug("Nic is plugged successfully for vm {} in network {}. VM is a part of network now.", vm, network); + final long isDefault = nic.isDefaultNic() ? 1 : 0; + + if (VirtualMachine.Type.User.equals(vmVO.getType())) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_ASSIGN, vmVO.getAccountId(), vmVO.getDataCenterId(), vmVO.getId(), + Long.toString(nic.getId()), network.getNetworkOfferingId(), null, isDefault, VirtualMachine.class.getName(), vmVO.getUuid(), vm.isDisplay()); + } + return nic; + } else { + logger.warn("Failed to plug NIC to the Instance {} in Network {}", vm, network); + return null; + } + } finally { + if (!result) { + logger.debug("Removing NIC {} from Instance {} as NIC plug failed on the backend.", nic, vmProfile.getVirtualMachine()); + networkMgr.removeNic(vmProfile, nicsDao.findById(nic.getId())); + } + } + } else if (vm.getState() == State.Stopped) { + return networkMgr.createNicForVm(network, requested, context, vmProfile, false); + } else { + logger.warn("Unable to add vm {} to network {}", vm, network); + throw new ResourceUnavailableException("Unable to add vm " + vm + " to network, is not in the right state", DataCenter.class, vm.getDataCenterId()); + } + } + + /** + * duplicated in {@see UserVmManagerImpl} for a {@see UserVmVO} + */ + @Override + public void checkIfNetworkExistsForUserVM(VirtualMachine virtualMachine, Network network) { + if (virtualMachine.getType() != VirtualMachine.Type.User) { + return; // others may have multiple nics in the same network + } + List allNics = nicsDao.listByVmId(virtualMachine.getId()); + for (NicVO nic : allNics) { + if (nic.getNetworkId() == network.getId()) { + throw new CloudRuntimeException("A NIC already exists for VM:" + virtualMachine.getInstanceName() + " in network: " + network.getUuid()); + } + } + } + + @Override + public NicTO toNicTO(final NicProfile nic, final HypervisorType hypervisorType) { + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(hypervisorType); + return hvGuru.toNicTO(nic); + } + + @Override + public boolean removeNicFromVm(final VirtualMachine vm, final Nic nic, BackendNicOperations backendNicOperations) + throws ConcurrentOperationException, ResourceUnavailableException { + final CallContext cctx = CallContext.current(); + final VMInstanceVO vmVO = vmDao.findById(vm.getId()); + final NetworkVO network = networkDao.findById(nic.getNetworkId()); + final ReservationContext context = new ReservationContextImpl(null, null, cctx.getCallingUser(), cctx.getCallingAccount()); + + final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); + + final DataCenter dc = entityMgr.findById(DataCenter.class, network.getDataCenterId()); + final Host host = hostDao.findById(vm.getHostId()); + final DeployDestination dest = new DeployDestination(dc, null, null, host); + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); + final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); + + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkModel.getNetworkRate(network.getId(), vm.getId()), + networkModel.isSecurityGroupSupportedInNetwork(network), networkModel.getNetworkTag(vmProfile.getVirtualMachine().getHypervisorType(), network)); + + if (vm.getState() == State.Running) { + final NicTO nicTO = toNicTO(nicProfile, vmProfile.getVirtualMachine().getHypervisorType()); + logger.debug("Un-plugging NIC {} for Instance {} from Network {}.", nic, vm, network); + final boolean result = backendNicOperations.unplugNic(network, nicTO, vmTO, context, dest); + if (result) { + userVmMgr.setupVmForPvlan(false, vm.getHostId(), nicProfile); + logger.debug("NIC is unplugged successfully for Instance {} in Network {}.", vm, network); + final long isDefault = nic.isDefaultNic() ? 1 : 0; + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, vm.getAccountId(), vm.getDataCenterId(), vm.getId(), + Long.toString(nic.getId()), network.getNetworkOfferingId(), null, isDefault, VirtualMachine.class.getName(), vm.getUuid(), vm.isDisplay()); + } else { + logger.warn("Failed to unplug NIC for the Instance {} from Network {}.", vm, network); + return false; + } + } else if (vm.getState() != State.Stopped) { + logger.warn("Unable to remove Instance {} from Network {}", vm, network); + throw new ResourceUnavailableException("Unable to remove Instance " + vm + " from Network, is not in the right state", DataCenter.class, vm.getDataCenterId()); + } + + networkMgr.releaseNic(vmProfile, nic); + logger.debug("Successfully released NIC {} for Instance {}", nic, vm); + + networkMgr.removeNic(vmProfile, nic); + nicsDao.remove(nic.getId()); + return true; + } + + @Override + @DB + public boolean removeVmFromNetwork(final VirtualMachine vm, final Network network, final URI broadcastUri, BackendNicOperations backendNicOperations) + throws ConcurrentOperationException, ResourceUnavailableException { + final CallContext cctx = CallContext.current(); + final VMInstanceVO vmVO = vmDao.findById(vm.getId()); + final ReservationContext context = new ReservationContextImpl(null, null, cctx.getCallingUser(), cctx.getCallingAccount()); + + final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vmVO, null, null, null, null); + + final DataCenter dc = entityMgr.findById(DataCenter.class, network.getDataCenterId()); + final Host host = hostDao.findById(vm.getHostId()); + final DeployDestination dest = new DeployDestination(dc, null, null, host); + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(vmProfile.getVirtualMachine().getHypervisorType()); + final VirtualMachineTO vmTO = hvGuru.implement(vmProfile); + + Nic nic = null; + if (broadcastUri != null) { + nic = nicsDao.findByNetworkIdInstanceIdAndBroadcastUri(network.getId(), vm.getId(), broadcastUri.toString()); + } else { + nic = networkModel.getNicInNetwork(vm.getId(), network.getId()); + } + + if (nic == null) { + logger.warn("Could not get a NIC with {}", network); + return false; + } + + if (nic.isDefaultNic() && vm.getType() == VirtualMachine.Type.User) { + logger.warn("Failed to remove NIC from {} in {}, NIC is default.", vm, network); + throw new CloudRuntimeException("Failed to remove NIC from " + vm + " in " + network + ", NIC is default."); + } + + final Nic lock = nicsDao.acquireInLockTable(nic.getId()); + if (lock == null) { + if (nicsDao.findById(nic.getId()) == null) { + logger.debug("Not need to remove the vm {} from network {} as the vm doesn't have nic in this network.", vm, network); + return true; + } + throw new ConcurrentOperationException(String.format("Unable to lock nic %s", nic)); + } + + logger.debug("Lock is acquired for nic {} as a part of remove vm {} from network {}", lock, vm, network); + + try { + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkModel.getNetworkRate(network.getId(), vm.getId()), + networkModel.isSecurityGroupSupportedInNetwork(network), networkModel.getNetworkTag(vmProfile.getVirtualMachine().getHypervisorType(), network)); + + if (vm.getState() == State.Running) { + final NicTO nicTO = toNicTO(nicProfile, vmProfile.getVirtualMachine().getHypervisorType()); + logger.debug("Un-plugging nic for vm {} from network {}", vm, network); + final boolean result = backendNicOperations.unplugNic(network, nicTO, vmTO, context, dest); + if (result) { + logger.debug("Nic is unplugged successfully for vm {} in network {}", vm, network); + } else { + logger.warn("Failed to unplug nic for the vm {} from network {}", vm, network); + return false; + } + } else if (vm.getState() != State.Stopped) { + logger.warn("Unable to remove vm {} from network {}", vm, network); + throw new ResourceUnavailableException("Unable to remove vm " + vm + " from network, is not in the right state", DataCenter.class, vm.getDataCenterId()); + } + + networkMgr.releaseNic(vmProfile, nic); + logger.debug("Successfully released nic {} for vm {}", nic, vm); + + networkMgr.removeNic(vmProfile, nic); + return true; + } finally { + nicsDao.releaseFromLockTable(lock.getId()); + logger.debug("Lock is released for nic {} as a part of remove vm {} from network {}", lock, vm, network); + } + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkNameMappingService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkNameMappingService.java new file mode 100644 index 000000000000..fd8ddb0e3201 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkNameMappingService.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public interface VmNetworkNameMappingService { + + void setVmNetworkDetails(VMInstanceVO vm, VirtualMachineTO vmTO); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkNameMappingServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkNameMappingServiceImpl.java new file mode 100644 index 000000000000..4a38f1cee033 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNetworkNameMappingServiceImpl.java @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.domain.Domain; +import com.cloud.domain.dao.DomainDao; +import com.cloud.network.Networks; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.user.Account; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +public class VmNetworkNameMappingServiceImpl implements VmNetworkNameMappingService { + + @Inject + protected UserVmJoinDao userVmJoinDao; + @Inject + protected DomainRouterJoinDao domainRouterJoinDao; + @Inject + protected NetworkDao networkDao; + @Inject + protected AccountDao accountDao; + @Inject + protected DomainDao domainDao; + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected VpcDao vpcDao; + + @Override + public void setVmNetworkDetails(VMInstanceVO vm, VirtualMachineTO vmTO) { + Map networkToNetworkNameMap = new HashMap<>(); + if (VirtualMachine.Type.User.equals(vm.getType())) { + List userVmJoinVOs = userVmJoinDao.searchByIds(vm.getId()); + if (userVmJoinVOs != null && !userVmJoinVOs.isEmpty()) { + for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) { + addToNetworkNameMap(userVmJoinVO.getNetworkId(), vm.getDataCenterId(), networkToNetworkNameMap); + } + vmTO.setNetworkIdToNetworkNameMap(networkToNetworkNameMap); + } + } else if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { + List routerJoinVO = domainRouterJoinDao.getRouterByIdAndTrafficType(vm.getId(), Networks.TrafficType.Guest); + for (DomainRouterJoinVO router : routerJoinVO) { + NetworkVO guestNetwork = networkDao.findById(router.getNetworkId()); + if (guestNetwork.getVpcId() == null && guestNetwork.getBroadcastDomainType() == Networks.BroadcastDomainType.NSX) { + addToNetworkNameMap(router.getNetworkId(), vm.getDataCenterId(), networkToNetworkNameMap); + } + } + vmTO.setNetworkIdToNetworkNameMap(networkToNetworkNameMap); + } + } + + private void addToNetworkNameMap(long networkId, long dataCenterId, Map networkToNetworkNameMap) { + NetworkVO networkVO = networkDao.findById(networkId); + Account acc = accountDao.findById(networkVO.getAccountId()); + Domain domain = domainDao.findById(networkVO.getDomainId()); + DataCenter zone = dataCenterDao.findById(dataCenterId); + if (Objects.isNull(zone)) { + throw new CloudRuntimeException(String.format("Failed to find zone with ID: %s", dataCenterId)); + } + if (Objects.isNull(acc)) { + throw new CloudRuntimeException(String.format("Failed to find account with ID: %s", networkVO.getAccountId())); + } + if (Objects.isNull(domain)) { + throw new CloudRuntimeException(String.format("Failed to find domain with ID: %s", networkVO.getDomainId())); + } + String networkName = String.format("D%s-A%s-Z%s", domain.getId(), acc.getId(), zone.getId()); + if (Objects.isNull(networkVO.getVpcId())) { + networkName += "-S" + networkVO.getId(); + } else { + VpcVO vpc = vpcDao.findById(networkVO.getVpcId()); + if (Objects.isNull(vpc)) { + throw new CloudRuntimeException(String.format("Failed to find VPC with ID: %s", networkVO.getVpcId())); + } + networkName = String.format("%s-V%s-S%s", networkName, vpc.getId(), networkVO.getId()); + } + networkToNetworkNameMap.put(networkVO.getId(), networkName); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNicBackendCommandService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNicBackendCommandService.java new file mode 100644 index 000000000000..8aee2345ecd3 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNicBackendCommandService.java @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.network.Network; + +public interface VmNicBackendCommandService { + + boolean replugNic(Network network, NicTO nic, VirtualMachineTO vm, Host host) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException; + + boolean plugNic(Network network, NicTO nic, VirtualMachineTO vm, ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException; + + boolean unplugNic(Network network, NicTO nic, VirtualMachineTO vm, ReservationContext context, DeployDestination dest) throws ConcurrentOperationException, + ResourceUnavailableException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNicBackendCommandServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNicBackendCommandServiceImpl.java new file mode 100644 index 000000000000..0a95491e580c --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNicBackendCommandServiceImpl.java @@ -0,0 +1,185 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.HashMap; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.commons.collections.MapUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PlugNicAnswer; +import com.cloud.agent.api.PlugNicCommand; +import com.cloud.agent.api.ReplugNicAnswer; +import com.cloud.agent.api.ReplugNicCommand; +import com.cloud.agent.api.UnPlugNicAnswer; +import com.cloud.agent.api.UnPlugNicCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DataCenter; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.network.Network; +import com.cloud.network.dao.NetworkDetailVO; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.offering.NetworkOffering; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmNicBackendCommandServiceImpl implements VmNicBackendCommandService { + + private static final Logger logger = LogManager.getLogger(VmNicBackendCommandServiceImpl.class); + + @Inject + protected AgentManager agentMgr; + @Inject + protected VMInstanceDao vmDao; + @Inject + protected UserVmDao userVmDao; + @Inject + protected UserVmService userVmService; + @Inject + protected NetworkDetailsDao networkDetailsDao; + @Inject + protected VmVlanPersistenceMappingService vmVlanPersistenceMappingService; + + @Override + public boolean replugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final Host host) throws ConcurrentOperationException, + ResourceUnavailableException, InsufficientCapacityException { + boolean result = true; + + final VMInstanceVO router = vmDao.findById(vm.getId()); + if (router.getState() == State.Running) { + try { + final ReplugNicCommand replugNicCmd = new ReplugNicCommand(nic, vm.getName(), vm.getType(), vm.getDetails()); + final Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand("replugnic", replugNicCmd); + agentMgr.send(host.getId(), cmds); + final ReplugNicAnswer replugNicAnswer = cmds.getAnswer(ReplugNicAnswer.class); + if (replugNicAnswer == null || !replugNicAnswer.getResult()) { + logger.warn("Unable to replug nic for vm {}", vm.getName()); + result = false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException("Unable to plug nic for router " + vm.getName() + " in network " + network, host.getId(), e); + } + } else { + String message = String.format("Unable to apply ReplugNic, VM [%s] is not in the right state (\"Running\"). VM state [%s].", router.toString(), router.getState()); + logger.warn(message); + + throw new ResourceUnavailableException(message, DataCenter.class, router.getDataCenterId()); + } + + return result; + } + + @Override + public boolean plugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final ReservationContext context, final DeployDestination dest) + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { + boolean result = true; + + final VMInstanceVO router = vmDao.findById(vm.getId()); + if (router.getState() == State.Running) { + try { + NetworkDetailVO pvlanTypeDetail = networkDetailsDao.findDetail(network.getId(), ApiConstants.ISOLATED_PVLAN_TYPE); + if (pvlanTypeDetail != null) { + Map nicDetails = nic.getDetails() == null ? new HashMap<>() : nic.getDetails(); + logger.debug("Found PVLAN type: {} on network details, adding it as part of the PlugNicCommand", pvlanTypeDetail.getValue()); + nicDetails.putIfAbsent(NetworkOffering.Detail.pvlanType, pvlanTypeDetail.getValue()); + nic.setDetails(nicDetails); + } + final PlugNicCommand plugNicCmd = new PlugNicCommand(nic, vm.getName(), vm.getType(), vm.getDetails()); + final Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand("plugnic", plugNicCmd); + agentMgr.send(dest.getHost().getId(), cmds); + final PlugNicAnswer plugNicAnswer = cmds.getAnswer(PlugNicAnswer.class); + if (plugNicAnswer == null || !plugNicAnswer.getResult()) { + logger.warn("Unable to plug nic for vm {}", vm.getName()); + result = false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException("Unable to plug nic for router " + vm.getName() + " in network " + network, dest.getHost().getId(), e); + } + } else { + String message = String.format("Unable to apply PlugNic, VM [%s] is not in the right state (\"Running\"). VM state [%s].", router.toString(), router.getState()); + logger.warn(message); + + throw new ResourceUnavailableException(message, DataCenter.class, + router.getDataCenterId()); + } + + return result; + } + + @Override + public boolean unplugNic(final Network network, final NicTO nic, final VirtualMachineTO vm, final ReservationContext context, final DeployDestination dest) + throws ConcurrentOperationException, ResourceUnavailableException { + + boolean result = true; + final VMInstanceVO router = vmDao.findById(vm.getId()); + + if (router.getState() == State.Running) { + UserVmVO userVm = userVmDao.findById(vm.getId()); + if (userVm != null && userVm.getType() == VirtualMachine.Type.User) { + userVmService.collectVmNetworkStatistics(userVm); + } + try { + final Commands cmds = new Commands(Command.OnError.Stop); + final UnPlugNicCommand unplugNicCmd = new UnPlugNicCommand(nic, vm.getName()); + Map vlanToPersistenceMap = vmVlanPersistenceMappingService.getVlanToPersistenceMapForVM(vm.getId()); + if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { + unplugNicCmd.setVlanToPersistenceMap(vlanToPersistenceMap); + } + cmds.addCommand("unplugnic", unplugNicCmd); + agentMgr.send(dest.getHost().getId(), cmds); + + final UnPlugNicAnswer unplugNicAnswer = cmds.getAnswer(UnPlugNicAnswer.class); + if (unplugNicAnswer == null || !unplugNicAnswer.getResult()) { + logger.warn("Unable to unplug nic from router {}", router); + result = false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException("Unable to unplug nic from rotuer " + router + " from network " + network, dest.getHost().getId(), e); + } + } else if (router.getState() == State.Stopped || router.getState() == State.Stopping) { + logger.debug("Vm {} is in {}, so not sending unplug nic command to the backend", router.getInstanceName(), router.getState()); + } else { + String message = String.format("Unable to apply unplug nic, VM [%s] is not in the right state (\"Running\"). VM state [%s].", router.toString(), router.getState()); + logger.warn(message); + + throw new ResourceUnavailableException(message, DataCenter.class, router.getDataCenterId()); + } + + return result; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNicUpdateService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNicUpdateService.java new file mode 100644 index 000000000000..89ab5f7637e6 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNicUpdateService.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.exception.ResourceUnavailableException; + +public interface VmNicUpdateService { + + Boolean updateDefaultNicForVM(VirtualMachine vm, Nic nic, Nic defaultNic); + + boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) throws ResourceUnavailableException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmNicUpdateServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmNicUpdateServiceImpl.java new file mode 100644 index 000000000000..5b23f6bfa50a --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmNicUpdateServiceImpl.java @@ -0,0 +1,91 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.UpdateVmNicAnswer; +import com.cloud.agent.api.UpdateVmNicCommand; +import com.cloud.agent.manager.Commands; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.NicDao; + +@Component +public class VmNicUpdateServiceImpl implements VmNicUpdateService { + + private static final Logger logger = LogManager.getLogger(VmNicUpdateServiceImpl.class); + + @Inject + protected AgentManager agentMgr; + @Inject + protected NicDao nicsDao; + + @Override + public Boolean updateDefaultNicForVM(final VirtualMachine vm, final Nic nic, final Nic defaultNic) { + logger.debug("Updating default nic of vm {} from nic {} to nic {}", vm, defaultNic.getUuid(), nic.getUuid()); + Integer chosenID = nic.getDeviceId(); + Integer existingID = defaultNic.getDeviceId(); + NicVO nicVO = nicsDao.findById(nic.getId()); + NicVO defaultNicVO = nicsDao.findById(defaultNic.getId()); + + nicVO.setDefaultNic(true); + nicVO.setDeviceId(existingID); + defaultNicVO.setDefaultNic(false); + defaultNicVO.setDeviceId(chosenID); + + nicsDao.persist(nicVO); + nicsDao.persist(defaultNicVO); + return true; + } + + @Override + public boolean updateVmNic(final VirtualMachine vm, final Nic nic, final Boolean enabled) throws ResourceUnavailableException { + if (vm.getState() == State.Running) { + try { + UpdateVmNicCommand updateVmNicCmd = new UpdateVmNicCommand(nic.getMacAddress(), vm.getName(), enabled); + Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand("updatevmnic", updateVmNicCmd); + + agentMgr.send(vm.getHostId(), cmds); + + UpdateVmNicAnswer updateVmNicAnswer = cmds.getAnswer(UpdateVmNicAnswer.class); + if (updateVmNicAnswer == null || !updateVmNicAnswer.getResult()) { + logger.warn("Unable to update VM {} NIC [{}].", vm.getName(), nic.getUuid()); + return false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException(String.format("Unable to update NIC %s for VM %s.", nic.getUuid(), vm.getUuid()), vm.getHostId(), e); + } + } + + NicVO nicVo = nicsDao.findById(nic.getId()); + nicVo.setEnabled(enabled); + nicsDao.persist(nicVo); + + return true; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmOfflineStorageMigrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmOfflineStorageMigrationService.java new file mode 100644 index 000000000000..c12820247138 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmOfflineStorageMigrationService.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +public interface VmOfflineStorageMigrationService { + + void orchestrateStorageMigration(String vmUuid, Map volumeToPool); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmOfflineStorageMigrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmOfflineStorageMigrationServiceImpl.java new file mode 100644 index 000000000000..0462da163602 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmOfflineStorageMigrationServiceImpl.java @@ -0,0 +1,365 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.MigrateVmToPoolAnswer; +import com.cloud.agent.api.UnregisterVMCommand; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.org.Cluster; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.Volume.Type; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmOfflineStorageMigrationServiceImpl implements VmOfflineStorageMigrationService { + + private static final Logger logger = LogManager.getLogger(VmOfflineStorageMigrationServiceImpl.class); + + @Inject + protected VMInstanceDao vmInstanceDao; + @Inject + protected VolumeDao volumeDao; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + @Inject + protected ClusterDao clusterDao; + @Inject + protected HostDao hostDao; + @Inject + protected DiskOfferingDao diskOfferingDao; + @Inject + protected ClusterDetailsDao clusterDetailsDao; + @Inject + protected AgentManager agentMgr; + @Inject + protected HypervisorGuruManager hvGuruMgr; + @Inject + protected NetworkOrchestrationService networkMgr; + @Inject + protected VolumeOrchestrationService volumeMgr; + @Inject + protected StorageManager storageMgr; + @Inject + protected VmVolumeMigrationPlanningService vmVolumeMigrationPlanningService; + @Inject + @Lazy + protected VirtualMachineManager virtualMachineManager; + + @Override + public void orchestrateStorageMigration(final String vmUuid, final Map volumeToPool) { + final VMInstanceVO vm = vmInstanceDao.findByUuid(vmUuid); + + try { + Map volumeToPoolMap = prepareVmStorageMigration(vm, volumeToPool); + + logger.debug("Offline migration of {} vm {} with volumes", + vm.getHypervisorType().toString(), + vm.getInstanceName()); + + migrateThroughHypervisorOrStorage(vm, volumeToPoolMap); + + } catch (ConcurrentOperationException + | InsufficientCapacityException + | StorageUnavailableException e) { + String msg = String.format("Failed to migrate VM: %s", vmUuid); + logger.warn(msg, e); + throw new CloudRuntimeException(msg, e); + } finally { + try { + virtualMachineManager.stateTransitTo(vm, Event.AgentReportStopped, null); + } catch (final NoTransitionException e) { + String anotherMEssage = String.format("failed to change vm state of VM: %s", vmUuid); + logger.warn(anotherMEssage, e); + throw new CloudRuntimeException(anotherMEssage, e); + } + } + } + + protected Answer[] attemptHypervisorMigration(VMInstanceVO vm, Map volumeToPool, Long hostId) { + if (hostId == null) { + return null; + } + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(vm.getHypervisorType()); + + List commandsToSend = hvGuru.finalizeMigrate(vm, volumeToPool); + + if (CollectionUtils.isNotEmpty(commandsToSend)) { + Commands commandsContainer = new Commands(Command.OnError.Stop); + commandsContainer.addCommands(commandsToSend); + + try { + return agentMgr.send(hostId, commandsContainer); + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.warn("Hypervisor migration failed for the VM: {}", vm, e); + } + } + return null; + } + + protected void afterHypervisorMigrationCleanup(VMInstanceVO vm, Map volumeToPool, Long sourceClusterId, Answer[] hypervisorMigrationResults) throws InsufficientCapacityException { + logger.debug("Cleaning up after hypervisor pool migration volumes for VM {}({})", vm.getInstanceName(), vm.getUuid()); + + StoragePool rootVolumePool = null; + if (MapUtils.isNotEmpty(volumeToPool)) { + for (Map.Entry entry : volumeToPool.entrySet()) { + if (Type.ROOT.equals(entry.getKey().getVolumeType())) { + rootVolumePool = entry.getValue(); + break; + } + } + } + setDestinationPoolAndReallocateNetwork(rootVolumePool, vm); + Long destClusterId = rootVolumePool != null ? rootVolumePool.getClusterId() : null; + if (destClusterId != null && !destClusterId.equals(sourceClusterId)) { + logger.debug("Resetting lastHost for VM {}({})", vm.getInstanceName(), vm.getUuid()); + vm.setLastHostId(null); + vm.setPodIdToDeployIn(rootVolumePool.getPodId()); + } + + markVolumesInPool(vm, hypervisorMigrationResults); + } + + protected void markVolumesInPool(VMInstanceVO vm, Answer[] hypervisorMigrationResults) { + MigrateVmToPoolAnswer relevantAnswer = null; + if (hypervisorMigrationResults.length == 1 && !hypervisorMigrationResults[0].getResult()) { + throw new CloudRuntimeException(String.format("VM ID: %s migration failed. %s", vm.getUuid(), hypervisorMigrationResults[0].getDetails())); + } + for (Answer answer : hypervisorMigrationResults) { + logger.debug("Received an {}: {}", answer.getClass().getSimpleName(), answer); + if (answer instanceof MigrateVmToPoolAnswer) { + relevantAnswer = (MigrateVmToPoolAnswer) answer; + } + } + if (relevantAnswer == null) { + throw new CloudRuntimeException("No relevant migration results found"); + } + List results = relevantAnswer.getVolumeTos(); + if (results == null) { + results = new ArrayList<>(); + } + List volumes = volumeDao.findUsableVolumesForInstance(vm.getId()); + logger.debug("Found {} volumes for VM {}(uuid:{}, id:{})", results.size(), vm.getInstanceName(), vm.getUuid(), vm.getId()); + for (VolumeObjectTO result : results) { + logger.debug("Updating volume ({}) with path '{}' on pool '{}'", result.getUuid(), result.getPath(), result.getDataStoreUuid()); + VolumeVO volume = volumeDao.findById(result.getId()); + StoragePool pool = storagePoolDao.findPoolByUUID(result.getDataStoreUuid()); + if (volume == null || pool == null) { + continue; + } + volume.setPath(result.getPath()); + volume.setPoolId(pool.getId()); + volume.setPoolType(pool.getPoolType()); + if (result.getChainInfo() != null) { + volume.setChainInfo(result.getChainInfo()); + } + volumeDao.update(volume.getId(), volume); + } + } + + protected void migrateThroughHypervisorOrStorage(VMInstanceVO vm, Map volumeToPool) throws StorageUnavailableException, InsufficientCapacityException { + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + Pair vmClusterAndHost = virtualMachineManager.findClusterAndHostIdForVm(vm, false); + final Long sourceClusterId = vmClusterAndHost.first(); + final Long sourceHostId = vmClusterAndHost.second(); + Answer[] hypervisorMigrationResults = attemptHypervisorMigration(vm, volumeToPool, sourceHostId); + boolean migrationResult = false; + if (hypervisorMigrationResults == null) { + migrationResult = volumeMgr.storageMigration(profile, volumeToPool); + if (migrationResult) { + postStorageMigrationCleanup(vm, volumeToPool, hostDao.findById(sourceHostId), sourceClusterId); + } else { + logger.debug("Storage migration failed"); + } + } else { + afterHypervisorMigrationCleanup(vm, volumeToPool, sourceClusterId, hypervisorMigrationResults); + } + } + + protected Map prepareVmStorageMigration(VMInstanceVO vm, Map volumeToPool) { + Map volumeToPoolMap = new HashMap<>(); + if (MapUtils.isEmpty(volumeToPool)) { + throw new CloudRuntimeException(String.format("Unable to migrate %s: missing volume to pool mapping.", vm.toString())); + } + Cluster cluster = null; + Long dataCenterId = null; + for (Map.Entry entry: volumeToPool.entrySet()) { + StoragePool pool = storagePoolDao.findById(entry.getValue()); + if (pool.getClusterId() != null) { + cluster = clusterDao.findById(pool.getClusterId()); + break; + } + dataCenterId = pool.getDataCenterId(); + } + Long podId = null; + Long clusterId = null; + if (cluster != null) { + dataCenterId = cluster.getDataCenterId(); + podId = cluster.getPodId(); + clusterId = cluster.getId(); + } + if (dataCenterId == null) { + String msg = "Unable to migrate Instance: failed to create deployment destination with given volume to pool map"; + logger.debug(msg); + throw new CloudRuntimeException(msg); + } + final DataCenterDeployment destination = new DataCenterDeployment(dataCenterId, podId, clusterId, null, null, null); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + volumeToPoolMap = vmVolumeMigrationPlanningService.createMappingVolumeAndStoragePool(profile, destination, volumeToPool); + try { + virtualMachineManager.stateTransitTo(vm, Event.StorageMigrationRequested, null); + } catch (final NoTransitionException e) { + String msg = String.format("Unable to migrate Instance: %s", vm.getUuid()); + logger.warn(msg, e); + throw new CloudRuntimeException(msg, e); + } + return volumeToPoolMap; + } + + protected void checkDestinationForTags(StoragePool destPool, VMInstanceVO vm) { + List vols = volumeDao.findUsableVolumesForInstance(vm.getId()); + + List storageTags = storageMgr.getStoragePoolTagList(destPool.getId()); + for (Volume vol : vols) { + DiskOfferingVO diskOffering = diskOfferingDao.findById(vol.getDiskOfferingId()); + List volumeTags = StringUtils.csvTagsToList(diskOffering.getTags()); + if (!matches(volumeTags, storageTags)) { + String msg = String.format("destination pool '%s' with tags '%s', does not support the volume diskoffering for volume '%s' (tags: '%s') ", + destPool.getName(), + StringUtils.listToCsvTags(storageTags), + vol.getName(), + StringUtils.listToCsvTags(volumeTags) + ); + throw new CloudRuntimeException(msg); + } + } + } + + static boolean matches(List volumeTags, List storagePoolTags) { + boolean result = true; + if (volumeTags != null) { + for (String tag : volumeTags) { + if (storagePoolTags == null || !storagePoolTags.contains(tag)) { + result = false; + break; + } + } + } + return result; + } + + protected void postStorageMigrationCleanup(VMInstanceVO vm, Map volumeToPool, HostVO srcHost, Long srcClusterId) throws InsufficientCapacityException { + StoragePool rootVolumePool = null; + if (MapUtils.isNotEmpty(volumeToPool)) { + for (Map.Entry entry : volumeToPool.entrySet()) { + if (Type.ROOT.equals(entry.getKey().getVolumeType())) { + rootVolumePool = entry.getValue(); + break; + } + } + } + setDestinationPoolAndReallocateNetwork(rootVolumePool, vm); + + vm.setLastHostId(null); + if (rootVolumePool != null) { + vm.setPodIdToDeployIn(rootVolumePool.getPodId()); + } + + if (vm.getHypervisorType().equals(HypervisorType.VMware)) { + afterStorageMigrationVmwareVMCleanup(rootVolumePool, vm, srcHost, srcClusterId); + } + } + + protected void setDestinationPoolAndReallocateNetwork(StoragePool destPool, VMInstanceVO vm) throws InsufficientCapacityException { + if (destPool != null && destPool.getPodId() != null && !destPool.getPodId().equals(vm.getPodIdToDeployIn())) { + logger.debug("as the pod for vm {} has changed we are reallocating its network", vm.getInstanceName()); + final DataCenterDeployment plan = new DataCenterDeployment(vm.getDataCenterId(), destPool.getPodId(), null, null, null, null); + final VirtualMachineProfileImpl vmProfile = new VirtualMachineProfileImpl(vm, null, null, null, null); + networkMgr.reallocate(vmProfile, plan); + } + } + + protected void afterStorageMigrationVmwareVMCleanup(StoragePool destPool, VMInstanceVO vm, HostVO srcHost, Long srcClusterId) { + final Long destClusterId = destPool.getClusterId(); + if (srcClusterId != null && destClusterId != null && !srcClusterId.equals(destClusterId) && srcHost != null) { + final String srcDcName = clusterDetailsDao.getVmwareDcName(srcClusterId); + final String destDcName = clusterDetailsDao.getVmwareDcName(destClusterId); + if (srcDcName != null && destDcName != null && !srcDcName.equals(destDcName)) { + removeStaleVmFromSource(vm, srcHost); + } + } + } + + protected void removeStaleVmFromSource(VMInstanceVO vm, HostVO srcHost) { + logger.debug("Since VM's storage was successfully migrated across VMware Datacenters, unregistering VM: {} from source host: {}", + vm, srcHost); + final UnregisterVMCommand uvc = new UnregisterVMCommand(vm.getInstanceName()); + uvc.setCleanupVmFiles(true); + try { + agentMgr.send(srcHost.getId(), uvc); + } catch (AgentUnavailableException | OperationTimedoutException e) { + throw new CloudRuntimeException(String.format( + "Failed to unregister VM: %s from source host: %s after successfully migrating VM's storage across VMware Datacenters", + vm, srcHost), e); + } + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmPowerStateSyncManager.java b/engine/orchestration/src/main/java/com/cloud/vm/VmPowerStateSyncManager.java new file mode 100644 index 000000000000..0682d6087ccb --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmPowerStateSyncManager.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +/** + * Handles out-of-band VM power-state reports and scanning for stalled + * VMs in transitional states. + * + * Extracted from {@link VirtualMachineManagerImpl} (Phase 4, slice 6). + */ +public interface VmPowerStateSyncManager { + + /** + * Processes a VM power-state report (routed from the message-bus handler on the god class). + * + * @param vmId the VM whose power state changed + */ + void handlePowerStateReport(Long vmId); + + /** + * Scans VMs in transition states on an UP host and resolves them. + * + * @param hostId the agent/host ID + */ + void scanStalledVMInTransitionStateOnUpHost(long hostId); + + /** + * Scans VMs in transition states on disconnected hosts and sends alerts. + */ + void scanStalledVMInTransitionStateOnDisconnectedHosts(); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmPowerStateSyncManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmPowerStateSyncManagerImpl.java new file mode 100644 index 000000000000..3c40a8e9cb7e --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmPowerStateSyncManagerImpl.java @@ -0,0 +1,368 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.stream.Collectors; + +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.jobs.JobInfo; +import org.apache.cloudstack.utils.cache.SingleCache; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.alert.AlertManager; +import com.cloud.event.ActionEventUtils; +import com.cloud.event.EventTypes; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.utils.DateUtil; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +/** + * Handles out-of-band VM power-state reports and scanning of stalled + * transitional VMs. Extracted from {@link VirtualMachineManagerImpl} (Phase 4, slice 6). + */ +@Component +public class VmPowerStateSyncManagerImpl implements VmPowerStateSyncManager { + + private static final Logger logger = LogManager.getLogger(VmPowerStateSyncManagerImpl.class); + + private static final String VM_SYNC_ALERT_SUBJECT = "VM state sync alert"; + + @Inject + protected VMInstanceDao vmInstanceDao; + + @Inject + protected VmWorkJobDao workJobDao; + + @Inject + protected HighAvailabilityManager haMgr; + + @Inject + protected HostDao hostDao; + + @Inject + protected AlertManager alertMgr; + + @Inject + @Lazy + protected VmStateMachineActions vmStateMachineActions; + + protected SingleCache> vmIdsInProgressCache; + + protected boolean syncTransitioningVmPowerState; + + @PostConstruct + public void init() { + vmIdsInProgressCache = new SingleCache<>(10, workJobDao::listVmIdsWithPendingJob); + syncTransitioningVmPowerState = Boolean.TRUE.equals(VirtualMachineManager.VmSyncPowerStateTransitioning.value()); + } + + @Override + public void handlePowerStateReport(final Long vmId) { + assert vmId != null; + + final List pendingWorkJobs = workJobDao.listPendingWorkJobs( + VirtualMachine.Type.Instance, vmId); + if (CollectionUtils.isEmpty(pendingWorkJobs) && !haMgr.hasPendingHaWork(vmId)) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + if (vm != null) { + switch (vm.getPowerState()) { + case PowerOn: + handlePowerOnReportWithNoPendingJobsOnVM(vm); + break; + + case PowerOff: + case PowerReportMissing: + handlePowerOffReportWithNoPendingJobsOnVM(vm); + break; + case PowerUnknown: + default: + assert false; + break; + } + } else { + logger.warn("VM {} no longer exists when processing VM state report.", vmId); + } + } else { + logger.info("There is pending job or HA tasks working on the VM. vm: {}, postpone power-change report by resetting power-change counters.", () -> vmInstanceDao.findById(vmId)); + vmInstanceDao.resetVmPowerStateTracking(vmId); + } + } + + protected ApiCommandResourceType getApiCommandResourceTypeForVm(VirtualMachine vm) { + switch (vm.getType()) { + case DomainRouter: + return ApiCommandResourceType.DomainRouter; + case ConsoleProxy: + return ApiCommandResourceType.ConsoleProxy; + case SecondaryStorageVm: + return ApiCommandResourceType.SystemVm; + } + return ApiCommandResourceType.VirtualMachine; + } + + protected void handlePowerOnReportWithNoPendingJobsOnVM(final VMInstanceVO vm) { + switch (vm.getState()) { + case Starting: + logger.info("VM {} is at {} and we received a power-on report while there is no pending jobs on it.", vm.getInstanceName(), vm.getState()); + + try { + vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); + } catch (final NoTransitionException e) { + logger.warn("Unexpected VM state transition exception, race-condition?", e); + } + + logger.info("VM {} is sync-ed to at Running state according to power-on report from hypervisor.", vm.getInstanceName()); + + alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), + VM_SYNC_ALERT_SUBJECT, "VM " + vm.getHostName() + "(" + vm.getInstanceName() + + ") state is sync-ed (Starting -> Running) from out-of-context transition. VM network environment may need to be reset"); + break; + + case Running: + try { + if (vm.getHostId() != null && !vm.getHostId().equals(vm.getPowerHostId())) { + logger.info("Detected out of band VM migration from host {} to host {}", () -> hostDao.findById(vm.getHostId()), () -> hostDao.findById(vm.getPowerHostId())); + } + vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); + } catch (final NoTransitionException e) { + logger.warn("Unexpected VM state transition exception, race-condition?", e); + } + + break; + + case Stopping: + case Stopped: + logger.info("VM {} is at {} and we received a power-on report while there is no pending jobs on it.", vm.getInstanceName(), vm.getState()); + + try { + vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); + } catch (final NoTransitionException e) { + logger.warn("Unexpected VM state transition exception, race-condition?", e); + } + alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), + VM_SYNC_ALERT_SUBJECT, "VM " + vm.getHostName() + "(" + vm.getInstanceName() + ") state is sync-ed (" + vm.getState() + + " -> Running) from out-of-context transition. VM network environment may need to be reset"); + + ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, vm.getDomainId(), + EventTypes.EVENT_VM_START, "Out of band VM power on", vm.getId(), getApiCommandResourceTypeForVm(vm).toString()); + logger.info("VM {} is sync-ed to at Running state according to power-on report from hypervisor.", vm.getInstanceName()); + break; + + case Destroyed: + case Expunging: + logger.info("Receive power on report when Instance is in destroyed or expunging state. Instance: {}, state: {}.", vm, vm.getState()); + break; + + case Migrating: + logger.info("Instance {} is at {} and we received a power-on report while there is no pending jobs on it.", vm, vm.getState()); + try { + vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOnReport, vm.getPowerHostId()); + } catch (final NoTransitionException e) { + logger.warn("Unexpected Instance state transition exception, race-condition?", e); + } + logger.info("Instance {} is sync-ed to at Running state according to power-on report from hypervisor.", vm); + break; + + case Error: + default: + logger.info("Receive power on report when Instance is in error or unexpected state. Instance: {}, state: {}.", vm, vm.getState()); + break; + } + } + + protected void handlePowerOffReportWithNoPendingJobsOnVM(final VMInstanceVO vm) { + switch (vm.getState()) { + case Starting: + case Stopping: + case Running: + case Stopped: + ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, vm.getDomainId(), + EventTypes.EVENT_VM_STOP, "Out of band VM power off", vm.getId(), getApiCommandResourceTypeForVm(vm).toString()); + case Migrating: + logger.info("VM {} is at {} and we received a {} report while there is no pending jobs on it" + , vm, vm.getState(), vm.getPowerState()); + if ((HighAvailabilityManager.ForceHA.value() || vm.isHaEnabled()) && vm.getState() == State.Running + && VirtualMachineManagerImpl.HaVmRestartHostUp.value() + && vm.getHypervisorType() != HypervisorType.VMware + && vm.getHypervisorType() != HypervisorType.Hyperv) { + logger.info("Detected out-of-band stop of a HA enabled VM {}, will schedule restart.", vm); + if (!haMgr.hasPendingHaWork(vm.getId())) { + haMgr.scheduleRestart(vm, true); + } else { + logger.info("VM {} already has a pending HA task working on it.", vm); + } + return; + } + + if (PowerState.PowerOff.equals(vm.getPowerState())) { + final VirtualMachineGuru vmGuru = vmStateMachineActions.getVmGuru(vm); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + if (!vmStateMachineActions.sendStop(vmGuru, profile, true, true)) { + return; + } else { + // Release resources on StopCommand success + vmStateMachineActions.releaseVmResources(profile, true); + } + } else if (PowerState.PowerReportMissing.equals(vm.getPowerState())) { + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + // VM will be sync-ed to Stopped state, release the resources + vmStateMachineActions.releaseVmResources(profile, true); + } + + try { + vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.FollowAgentPowerOffReport, null); + } catch (final NoTransitionException e) { + logger.warn("Unexpected VM state transition exception, race-condition?", e); + } + + alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), + VM_SYNC_ALERT_SUBJECT, String.format("VM %s(%s) state is sync-ed (%s -> Stopped) from out-of-context transition.", + vm.getHostName(), vm, vm.getState())); + + logger.info("VM {} is sync-ed to at Stopped state according to power-off report from hypervisor.", vm); + + break; + + case Destroyed: + case Expunging: + break; + + case Error: + default: + break; + } + } + + @Override + public void scanStalledVMInTransitionStateOnUpHost(final long hostId) { + if (!syncTransitioningVmPowerState) { + return; + } + if (!hostDao.isHostUp(hostId)) { + return; + } + final long stallThresholdInMs = VirtualMachineManagerImpl.VmJobStateReportInterval.value() * 2; + final long cutTime = new Date(DateUtil.currentGMTTime().getTime() - stallThresholdInMs).getTime(); + final List hostTransitionVms = vmInstanceDao.listByHostAndState(hostId, State.Starting, State.Stopping, State.Migrating); + + final List mostLikelyStoppedVMs = listStalledVMInTransitionStateOnUpHost(hostTransitionVms, cutTime); + for (final VMInstanceVO vm : mostLikelyStoppedVMs) { + handlePowerOffReportWithNoPendingJobsOnVM(vm); + } + + final List vmsWithRecentReport = listVMInTransitionStateWithRecentReportOnUpHost(hostTransitionVms, cutTime); + for (final VMInstanceVO vm : vmsWithRecentReport) { + if (vm.getPowerState() == PowerState.PowerOn) { + handlePowerOnReportWithNoPendingJobsOnVM(vm); + } else { + handlePowerOffReportWithNoPendingJobsOnVM(vm); + } + } + } + + @Override + public void scanStalledVMInTransitionStateOnDisconnectedHosts() { + final Date cutTime = new Date(DateUtil.currentGMTTime().getTime() - VirtualMachineManagerImpl.VmOpWaitInterval.value() * 1000); + final List stuckAndUncontrollableVMs = listStalledVMInTransitionStateOnDisconnectedHosts(cutTime); + for (final Long vmId : stuckAndUncontrollableVMs) { + final VMInstanceVO vm = vmInstanceDao.findById(vmId); + + alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_SYNC, vm.getDataCenterId(), vm.getPodIdToDeployIn(), + VM_SYNC_ALERT_SUBJECT, String.format("VM %s(%s) is stuck in %s state and its host is unreachable for too long", + vm.getHostName(), vm, vm.getState())); + } + } + + protected List listStalledVMInTransitionStateOnUpHost( + final List transitioningVms, final long cutTime) { + if (CollectionUtils.isEmpty(transitioningVms)) { + return transitioningVms; + } + List vmIdsInProgress = vmIdsInProgressCache.get(); + return transitioningVms.stream() + .filter(v -> v.getPowerStateUpdateTime().getTime() < cutTime && !vmIdsInProgress.contains(v.getId())) + .collect(Collectors.toList()); + } + + protected List listVMInTransitionStateWithRecentReportOnUpHost( + final List transitioningVms, final long cutTime) { + if (CollectionUtils.isEmpty(transitioningVms)) { + return transitioningVms; + } + List vmIdsInProgress = vmIdsInProgressCache.get(); + return transitioningVms.stream() + .filter(v -> v.getPowerStateUpdateTime().getTime() > cutTime && !vmIdsInProgress.contains(v.getId())) + .collect(Collectors.toList()); + } + + protected List listStalledVMInTransitionStateOnDisconnectedHosts(final Date cutTime) { + final String sql = "SELECT i.* " + + "FROM vm_instance AS i " + + "INNER JOIN host AS h ON i.host_id = h.id " + + "WHERE h.status != 'UP' " + + " AND i.power_state_update_time < ? " + + " AND i.state IN ('Starting', 'Stopping', 'Migrating') " + + " AND i.id NOT IN (SELECT vm_instance_id FROM vm_work_job AS w " + + " INNER JOIN async_job AS j ON w.id = j.id " + + " WHERE j.job_status = ?) " + + " AND i.removed IS NULL"; + + final List l = new ArrayList<>(); + TransactionLegacy txn = TransactionLegacy.currentTxn(); + String cutTimeStr = DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), cutTime); + int jobStatusInProgress = JobInfo.Status.IN_PROGRESS.ordinal(); + + try { + PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql); + + pstmt.setString(1, cutTimeStr); + pstmt.setInt(2, jobStatusInProgress); + final ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + l.add(rs.getLong(1)); + } + } catch (final SQLException e) { + logger.error("Unable to execute SQL [{}] with params {\"i.power_state_update_time\": \"{}\", \"j.job_status\": {}} due to [{}].", sql, cutTimeStr, jobStatusInProgress, e.getMessage(), e); + } + return l; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmRebootOrchestrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmRebootOrchestrationService.java new file mode 100644 index 000000000000..299f6f64f072 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmRebootOrchestrationService.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; + +public interface VmRebootOrchestrationService { + + void reboot(String vmUuid, Map params) + throws InsufficientCapacityException, ResourceUnavailableException; + + void advanceReboot(String vmUuid, Map params) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException; + + void orchestrateReboot(String vmUuid, Map params) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmRebootOrchestrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmRebootOrchestrationServiceImpl.java new file mode 100644 index 000000000000..962d100dd6b7 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmRebootOrchestrationServiceImpl.java @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DataCenter; +import com.cloud.dc.Pod; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.security.SecurityGroupManager; +import com.cloud.org.Cluster; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.StorageManager; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotManager; + +@Component +public class VmRebootOrchestrationServiceImpl implements VmRebootOrchestrationService { + + private static final Logger logger = LogManager.getLogger(VmRebootOrchestrationServiceImpl.class); + + @Inject + protected AgentManager agentMgr; + @Inject + protected VMInstanceDao vmDao; + @Inject + protected VMSnapshotManager vmSnapshotMgr; + @Inject + protected EntityManager entityMgr; + @Inject + protected HostDao hostDao; + @Inject + protected VmWorkJobQueueService vmWorkJobQueueService; + @Inject + protected VmCommandSpecPostProcessingService vmCommandSpecPostProcessingService; + @Inject + protected VmExternalProvisioningManager vmExternalProvisioningManager; + @Inject + protected SecurityGroupManager securityGroupManager; + @Inject + protected ResourceManager resourceMgr; + @Inject + protected NicDao nicsDao; + @Inject + protected NetworkModel networkModel; + @Inject + protected HypervisorGuruManager hvGuruMgr; + + @Override + public void reboot(final String vmUuid, final Map params) + throws InsufficientCapacityException, ResourceUnavailableException { + try { + advanceReboot(vmUuid, params); + } catch (final ConcurrentOperationException e) { + throw new CloudRuntimeException("Unable to reboot a VM due to concurrent operation", e); + } + } + + @Override + public void advanceReboot(final String vmUuid, final Map params) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + final VirtualMachine vm = vmDao.findByUuid(vmUuid); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + logger.debug("reboot parameter value of {} == {} at orchestration", VirtualMachineProfile.Param.BootIntoSetup.getName(), + (params == null ? "" : params.get(VirtualMachineProfile.Param.BootIntoSetup))); + orchestrateReboot(vmUuid, params); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); + } + } else { + logger.debug("reboot parameter value of {} == {} through job-queue", VirtualMachineProfile.Param.BootIntoSetup.getName(), + (params == null ? "" : params.get(VirtualMachineProfile.Param.BootIntoSetup))); + final Outcome outcome = vmWorkJobQueueService.rebootVmThroughJobQueue(vmUuid, params); + + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "rebootVm"); + + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } + } + + @Override + public void orchestrateReboot(final String vmUuid, final Map params) throws InsufficientCapacityException, + ConcurrentOperationException, ResourceUnavailableException { + final VMInstanceVO vm = vmDao.findByUuid(vmUuid); + if (vmSnapshotMgr.hasActiveVMSnapshotTasks(vm.getId())) { + logger.error("Unable to reboot Instance: {} due to: {} has active Instance Snapshot tasks", vm, vm.getInstanceName()); + throw new CloudRuntimeException("Unable to reboot Instance: " + vm + " due to: " + vm.getInstanceName() + " has active Instance Snapshots tasks"); + } + final DataCenter dc = entityMgr.findById(DataCenter.class, vm.getDataCenterId()); + final Host host = hostDao.findById(vm.getHostId()); + if (host == null) { + throw new CloudRuntimeException("Unable to retrieve host with id " + vm.getHostId()); + } + final Cluster cluster = entityMgr.findById(Cluster.class, host.getClusterId()); + final Pod pod = entityMgr.findById(Pod.class, host.getPodId()); + final DeployDestination dest = new DeployDestination(dc, pod, cluster, host); + + try { + final Commands cmds = new Commands(Command.OnError.Stop); + RebootCommand rebootCmd = new RebootCommand(vm.getInstanceName(), getExecuteInSequence(vm.getHypervisorType())); + VirtualMachineTO vmTo = getVmTO(vm.getId()); + vmCommandSpecPostProcessingService.setEnterSetupMode(vmTo, params); + rebootCmd.setVirtualMachine(vmTo); + vmExternalProvisioningManager.updateRebootCommandWithExternalDetails(host, vmTo, rebootCmd); + cmds.addCommand(rebootCmd); + agentMgr.send(host.getId(), cmds); + + final Answer rebootAnswer = cmds.getAnswer(RebootAnswer.class); + if (rebootAnswer != null && rebootAnswer.getResult()) { + boolean isVmSecurityGroupEnabled = securityGroupManager.isVmSecurityGroupEnabled(vm.getId()); + if (isVmSecurityGroupEnabled && vm.getType() == VirtualMachine.Type.User) { + List affectedVms = new ArrayList<>(); + affectedVms.add(vm.getId()); + securityGroupManager.scheduleRulesetUpdateToHosts(affectedVms, true, null); + } + if (vmTo.getGpuDevice() != null) { + resourceMgr.updateGPUDetailsForVmStart(host.getId(), vm.getId(), vmTo.getGpuDevice()); + } + return; + } + + String errorMsg = "Unable to reboot VM " + vm + " on " + dest.getHost() + " due to " + (rebootAnswer == null ? "no reboot response" : rebootAnswer.getDetails()); + logger.info(errorMsg); + throw new CloudRuntimeException(errorMsg); + } catch (final OperationTimedoutException e) { + logger.warn("Unable to send the reboot command to host {} for the vm {} due to operation timeout.", dest.getHost(), vm, e); + throw new CloudRuntimeException("Failed to reboot the vm on host " + dest.getHost(), e); + } + } + + protected VirtualMachineTO getVmTO(Long vmId) { + final VMInstanceVO vm = vmDao.findById(vmId); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + final List nics = nicsDao.listByVmId(profile.getId()); + Collections.sort(nics, new Comparator() { + @Override + public int compare(NicVO nic1, NicVO nic2) { + Long nicId1 = Long.valueOf(nic1.getDeviceId()); + Long nicId2 = Long.valueOf(nic2.getDeviceId()); + return nicId1.compareTo(nicId2); + } + }); + + for (final NicVO nic : nics) { + final Network network = networkModel.getNetwork(nic.getNetworkId()); + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, networkModel.isSecurityGroupSupportedInNetwork(network), + networkModel.getNetworkTag(profile.getHypervisorType(), network)); + profile.addNic(nicProfile); + } + final HypervisorGuru hvGuru = hvGuruMgr.getGuru(profile.getVirtualMachine().getHypervisorType()); + return hvGuru.implement(profile); + } + + protected boolean getExecuteInSequence(final HypervisorType hypervisorType) { + if (null == hypervisorType) { + return VirtualMachineManager.ExecuteInSequence.value(); + } + + if (Set.of(HypervisorType.KVM, HypervisorType.XenServer, HypervisorType.Hyperv, HypervisorType.LXC).contains(hypervisorType)) { + return false; + } else if (hypervisorType.equals(HypervisorType.VMware)) { + return StorageManager.shouldExecuteInSequenceOnVmware(); + } + return VirtualMachineManager.ExecuteInSequence.value(); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationActions.java b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationActions.java new file mode 100644 index 000000000000..4c5a1a3b1d53 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationActions.java @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.to.DpdkTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.ItWorkVO.Step; + +/** + * Narrow callback interface allowing the scale/reconfiguration service to reuse + * migration and state-machine helpers that still live on + * {@link VirtualMachineManagerImpl}. + */ +interface VmScaleReconfigurationActions { + + VirtualMachineGuru getVmGuru(VirtualMachine vm); + + VirtualMachineTO toVmTO(VirtualMachineProfile profile); + + boolean changeState(VMInstanceVO vm, VirtualMachine.Event event, Long hostId, ItWorkVO work, Step step) throws NoTransitionException; + + MigrateCommand buildMigrateCommand(VMInstanceVO vmInstance, VirtualMachineTO virtualMachineTO, DeployDestination destination, Answer answer, + Map dpdkInterfaceMapping); + + boolean checkVmOnHost(VirtualMachine vm, long hostId) throws AgentUnavailableException, OperationTimedoutException; + + Command cleanup(String vmName); + + boolean cleanup(VirtualMachineGuru guru, VirtualMachineProfile profile, ItWorkVO work, VirtualMachine.Event event, boolean cleanUpEvenIfUnableToStop); + + boolean stateTransitTo(VirtualMachine vm, VirtualMachine.Event event, Long hostId) throws NoTransitionException; + + void updateVmPod(VMInstanceVO vm, long dstHostId); + + long getNodeId(); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationActionsImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationActionsImpl.java new file mode 100644 index 000000000000..70e25cbf6878 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationActionsImpl.java @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import jakarta.inject.Inject; + +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.to.DpdkTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.ItWorkVO.Step; + +@Component +public class VmScaleReconfigurationActionsImpl implements VmScaleReconfigurationActions { + + @Inject + @Lazy + protected VirtualMachineManagerImpl virtualMachineManager; + + @Override + public VirtualMachineGuru getVmGuru(final VirtualMachine vm) { + return virtualMachineManager.getVmGuru(vm); + } + + @Override + public VirtualMachineTO toVmTO(final VirtualMachineProfile profile) { + return virtualMachineManager.toVmTO(profile); + } + + @Override + public boolean changeState(final VMInstanceVO vm, final VirtualMachine.Event event, final Long hostId, final ItWorkVO work, final Step step) + throws NoTransitionException { + return virtualMachineManager.changeState(vm, event, hostId, work, step); + } + + @Override + public MigrateCommand buildMigrateCommand(final VMInstanceVO vmInstance, final VirtualMachineTO virtualMachineTO, final DeployDestination destination, + final Answer answer, final Map dpdkInterfaceMapping) { + return virtualMachineManager.buildMigrateCommand(vmInstance, virtualMachineTO, destination, answer, dpdkInterfaceMapping); + } + + @Override + public boolean checkVmOnHost(final VirtualMachine vm, final long hostId) throws AgentUnavailableException, OperationTimedoutException { + return virtualMachineManager.checkVmOnHost(vm, hostId); + } + + @Override + public Command cleanup(final String vmName) { + return virtualMachineManager.cleanup(vmName); + } + + @Override + public boolean cleanup(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final ItWorkVO work, final VirtualMachine.Event event, + final boolean cleanUpEvenIfUnableToStop) { + return virtualMachineManager.cleanup(guru, profile, work, event, cleanUpEvenIfUnableToStop); + } + + @Override + public boolean stateTransitTo(final VirtualMachine vm, final VirtualMachine.Event event, final Long hostId) throws NoTransitionException { + return virtualMachineManager.stateTransitTo(vm, event, hostId); + } + + @Override + public void updateVmPod(final VMInstanceVO vm, final long dstHostId) { + virtualMachineManager.updateVmPod(vm, dstHostId); + } + + @Override + public long getNodeId() { + return virtualMachineManager.getNodeId(); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationService.java new file mode 100644 index 000000000000..91918f8aa989 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationService.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.offering.ServiceOffering; + +public interface VmScaleReconfigurationService { + + void findHostAndMigrate(String vmUuid, Long newSvcOfferingId, Map customParameters, DeploymentPlanner.ExcludeList excludes) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException; + + void migrateForScale(String vmUuid, long srcHostId, DeployDestination dest, Long oldSvcOfferingId) + throws ResourceUnavailableException, ConcurrentOperationException; + + void orchestrateMigrateForScale(String vmUuid, long srcHostId, DeployDestination dest, Long oldSvcOfferingId) + throws ResourceUnavailableException, ConcurrentOperationException; + + VMInstanceVO reConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, + Map customParameters, boolean reconfiguringOnExistingHost) + throws ResourceUnavailableException, InsufficientServerCapacityException, ConcurrentOperationException; + + VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, + boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationServiceImpl.java new file mode 100644 index 000000000000..2239496045f9 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmScaleReconfigurationServiceImpl.java @@ -0,0 +1,465 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; +import java.util.UUID; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.PrepareForMigrationCommand; +import com.cloud.agent.api.ScaleVmCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.Commands; +import com.cloud.alert.AlertManager; +import com.cloud.capacity.CapacityManager; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.event.EventTypes; +import com.cloud.exception.AffinityConflictException; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruBase; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.ItWorkVO.Step; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmScaleReconfigurationServiceImpl implements VmScaleReconfigurationService { + + private static final Logger logger = LogManager.getLogger(VmScaleReconfigurationServiceImpl.class); + + @Inject + protected VMInstanceDao vmDao; + @Inject + protected ServiceOfferingDao offeringDao; + @Inject + protected HostDao hostDao; + @Inject + protected DeploymentPlanningManager dpMgr; + @Inject + protected AgentManager agentMgr; + @Inject + protected NetworkOrchestrationService networkMgr; + @Inject + protected VolumeOrchestrationService volumeMgr; + @Inject + protected ItWorkDao workDao; + @Inject + protected AlertManager alertMgr; + @Inject + protected HypervisorGuruManager hvGuruMgr; + @Inject + protected UserVmManager userVmMgr; + @Inject + protected CapacityManager capacityMgr; + @Inject + protected HighAvailabilityManager haMgr; + @Inject + protected VmWorkJobQueueService vmWorkJobQueueService; + @Inject + protected VmServiceOfferingUpgradeManager vmServiceOfferingUpgradeManager; + @Inject + protected VmScaleReconfigurationActions vmScaleReconfigurationActions; + + @Override + public void findHostAndMigrate(final String vmUuid, final Long newSvcOfferingId, final Map customParameters, final ExcludeList excludes) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + + final VMInstanceVO vm = vmDao.findByUuid(vmUuid); + if (vm == null) { + throw new CloudRuntimeException("Unable to find " + vmUuid); + } + ServiceOfferingVO newServiceOffering = offeringDao.findById(newSvcOfferingId); + if (newServiceOffering.isDynamic()) { + newServiceOffering.setDynamicFlag(true); + newServiceOffering = offeringDao.getComputeOffering(newServiceOffering, customParameters); + } + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, newServiceOffering, null, null); + + final Long srcHostId = vm.getHostId(); + final Long oldSvcOfferingId = vm.getServiceOfferingId(); + if (srcHostId == null) { + throw new CloudRuntimeException("Unable to scale the vm because it doesn't have a host id"); + } + final Host host = hostDao.findById(srcHostId); + final DataCenterDeployment plan = new DataCenterDeployment(host.getDataCenterId(), host.getPodId(), host.getClusterId(), null, null, null); + excludes.addHost(vm.getHostId()); + vm.setServiceOfferingId(newSvcOfferingId); + + DeployDestination dest = null; + + try { + dest = dpMgr.planDeployment(profile, plan, excludes, null); + } catch (final AffinityConflictException e2) { + String message = String.format("Unable to create deployment, affinity rules associated to the %s conflict.", vm.toString()); + logger.warn(message, e2); + throw new CloudRuntimeException(message); + } + + if (dest != null) { + logger.debug("Found {} for scaling the vm to.", dest); + } + + if (dest == null) { + throw new InsufficientServerCapacityException("Unable to find a server to scale the vm to.", host.getClusterId()); + } + + excludes.addHost(dest.getHost().getId()); + try { + migrateForScale(vm.getUuid(), srcHostId, dest, oldSvcOfferingId); + } catch (ResourceUnavailableException | ConcurrentOperationException e) { + logger.warn("Unable to migrate {} to {} due to [{}]", vm.toString(), dest.getHost().toString(), e.getMessage(), e); + throw e; + } + } + + @Override + public void migrateForScale(final String vmUuid, final long srcHostId, final DeployDestination dest, final Long oldSvcOfferingId) + throws ResourceUnavailableException, ConcurrentOperationException { + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + final VirtualMachine vm = vmDao.findByUuid(vmUuid); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + orchestrateMigrateForScale(vmUuid, srcHostId, dest, oldSvcOfferingId); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); + } + } else { + final Outcome outcome = vmWorkJobQueueService.migrateVmForScaleThroughJobQueue(vmUuid, srcHostId, dest, oldSvcOfferingId); + + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "migrateVmForScale"); + + try { + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (InsufficientCapacityException ex) { + throw new RuntimeException("Unexpected exception", ex); + } + } + } + + @Override + public void orchestrateMigrateForScale(final String vmUuid, final long srcHostId, final DeployDestination dest, final Long oldSvcOfferingId) + throws ResourceUnavailableException, ConcurrentOperationException { + + VMInstanceVO vm = vmDao.findByUuid(vmUuid); + logger.info("Migrating {} to {}", vm, dest); + + vm.getServiceOfferingId(); + final long dstHostId = dest.getHost().getId(); + final Host fromHost = hostDao.findById(srcHostId); + if (fromHost == null) { + String logMessageUnableToFindHost = String.format("Unable to find host to migrate from %s.", srcHostId); + logger.info(logMessageUnableToFindHost); + throw new CloudRuntimeException(logMessageUnableToFindHost); + } + + Host dstHost = hostDao.findById(dstHostId); + long destHostClusterId = dest.getCluster().getId(); + long fromHostClusterId = fromHost.getClusterId(); + if (fromHostClusterId != destHostClusterId) { + String logMessageHostsOnDifferentCluster = String.format("Source and destination host are not in same cluster, unable to migrate to %s", fromHost); + logger.info(logMessageHostsOnDifferentCluster); + throw new CloudRuntimeException(logMessageHostsOnDifferentCluster); + } + + final VirtualMachineGuru vmGuru = vmScaleReconfigurationActions.getVmGuru(vm); + + vm = vmDao.findByUuid(vmUuid); + if (vm == null) { + String message = String.format("Unable to find VM {\"uuid\": \"%s\"}.", vmUuid); + logger.warn(message); + throw new CloudRuntimeException(message); + } + + if (vm.getState() != State.Running) { + String message = String.format("%s is not in \"Running\" state, unable to migrate it. Current state [%s].", vm.toString(), vm.getState()); + logger.warn(message); + throw new CloudRuntimeException(message); + } + + AlertManager.AlertType alertType = AlertManager.AlertType.ALERT_TYPE_USERVM_MIGRATE; + if (VirtualMachine.Type.DomainRouter.equals(vm.getType())) { + alertType = AlertManager.AlertType.ALERT_TYPE_DOMAIN_ROUTER_MIGRATE; + } else if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { + alertType = AlertManager.AlertType.ALERT_TYPE_CONSOLE_PROXY_MIGRATE; + } + + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + networkMgr.prepareNicForMigration(profile, dest); + + volumeMgr.prepareForMigration(profile, dest); + + final VirtualMachineTO to = vmScaleReconfigurationActions.toVmTO(profile); + final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to); + + ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), vmScaleReconfigurationActions.getNodeId(), State.Migrating, vm.getType(), vm.getId()); + work.setStep(Step.Prepare); + work.setResourceType(ItWorkVO.ResourceType.Host); + work.setResourceId(dstHostId); + work = workDao.persist(work); + + Answer pfma = null; + try { + pfma = agentMgr.send(dstHostId, pfmc); + if (pfma == null || !pfma.getResult()) { + final String details = pfma != null ? pfma.getDetails() : "null answer returned"; + pfma = null; + throw new AgentUnavailableException(String.format("Unable to prepare for migration to destination host [%s] due to [%s].", dest.getHost(), details), dstHostId); + } + } catch (final OperationTimedoutException e1) { + throw new AgentUnavailableException("Operation timed out", dstHostId); + } finally { + if (pfma == null) { + work.setStep(Step.Done); + workDao.update(work.getId(), work); + } + } + + vm.setLastHostId(srcHostId); + try { + if (vm.getHostId() == null || vm.getHostId() != srcHostId || !vmScaleReconfigurationActions.changeState(vm, Event.MigrationRequested, dstHostId, work, Step.Migrating)) { + String message = String.format("Migration of %s cancelled because state has changed.", vm.toString()); + logger.warn(message); + throw new ConcurrentOperationException(message); + } + } catch (final NoTransitionException e1) { + String message = String.format("Migration of %s cancelled due to [%s].", vm.toString(), e1.getMessage()); + logger.error(message, e1); + throw new ConcurrentOperationException(message); + } + + boolean migrated = false; + try { + final MigrateCommand mc = vmScaleReconfigurationActions.buildMigrateCommand(vm, to, dest, pfma, null); + + try { + final Answer ma = agentMgr.send(vm.getLastHostId(), mc); + if (ma == null || !ma.getResult()) { + String msg = String.format("Unable to migrate %s due to [%s].", vm.toString(), ma != null ? ma.getDetails() : "null answer returned"); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + } catch (final OperationTimedoutException e) { + if (e.isActive()) { + logger.warn("Active migration command so scheduling a restart for {}", vm, e); + haMgr.scheduleRestart(vm, true); + } + throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId, e); + } + + try { + final long newServiceOfferingId = vm.getServiceOfferingId(); + vm.setServiceOfferingId(oldSvcOfferingId); + if (!vmScaleReconfigurationActions.changeState(vm, VirtualMachine.Event.OperationSucceeded, dstHostId, work, Step.Started)) { + throw new ConcurrentOperationException("Unable to change the state for " + vm); + } + vm.setServiceOfferingId(newServiceOfferingId); + } catch (final NoTransitionException e1) { + throw new ConcurrentOperationException("Unable to change state due to " + e1.getMessage()); + } + + try { + if (!vmScaleReconfigurationActions.checkVmOnHost(vm, dstHostId)) { + logger.error("Unable to complete migration for {}", vm); + try { + agentMgr.send(srcHostId, new Commands(vmScaleReconfigurationActions.cleanup(vm.getInstanceName())), null); + } catch (final AgentUnavailableException e) { + logger.error("Unable to cleanup source host [{}] due to [{}].", fromHost, e.getMessage(), e); + } + vmScaleReconfigurationActions.cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.AgentReportStopped, true); + throw new CloudRuntimeException("Unable to complete migration for " + vm); + } + } catch (final OperationTimedoutException e) { + logger.debug("Error while checking the {} on {}", vm, dstHost, e); + } + + migrated = true; + } finally { + if (!migrated) { + logger.info("Migration was unsuccessful. Cleaning up: {}", vm); + + String alertSubject = String.format("Unable to migrate %s from %s in Zone [%s] and Pod [%s].", + vm.getInstanceName(), fromHost, dest.getDataCenter().getName(), dest.getPod().getName()); + String alertBody = "Migrate Command failed. Please check logs."; + alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), alertSubject, alertBody); + try { + agentMgr.send(dstHostId, new Commands(vmScaleReconfigurationActions.cleanup(vm.getInstanceName())), null); + } catch (final AgentUnavailableException ae) { + logger.info("Looks like the destination Host is unavailable for cleanup"); + } + networkMgr.setHypervisorHostname(profile, dest, false); + try { + vmScaleReconfigurationActions.stateTransitTo(vm, Event.OperationFailed, srcHostId); + } catch (final NoTransitionException e) { + logger.warn(e.getMessage(), e); + } + } else { + networkMgr.setHypervisorHostname(profile, dest, true); + + vmScaleReconfigurationActions.updateVmPod(vm, dstHostId); + } + + work.setStep(Step.Done); + workDao.update(work.getId(), work); + } + } + + @Override + public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, + Map customParameters, final boolean reconfiguringOnExistingHost) + throws ResourceUnavailableException, InsufficientServerCapacityException, ConcurrentOperationException { + + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + final VirtualMachine vm = vmDao.findByUuid(vmUuid); + VmWorkJobVO placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + return orchestrateReConfigureVm(vmUuid, oldServiceOffering, newServiceOffering, reconfiguringOnExistingHost); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); + } + } else { + final Outcome outcome = vmWorkJobQueueService.reconfigureVmThroughJobQueue(vmUuid, oldServiceOffering, newServiceOffering, customParameters, reconfiguringOnExistingHost); + + VirtualMachine vm = vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "reconfigureVm"); + + Object result = null; + try { + result = vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (Exception ex) { + throw new RuntimeException("Unhandled exception", ex); + } + + if (result != null) { + throw new RuntimeException(String.format("Unexpected job execution result [%s]", result)); + } + + return (VMInstanceVO)vm; + } + } + + @Override + public VMInstanceVO orchestrateReConfigureVm(final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, + final boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException { + final VMInstanceVO vm = vmDao.findByUuid(vmUuid); + + HostVO hostVo = hostDao.findById(vm.getHostId()); + + Long clustedId = hostVo.getClusterId(); + Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clustedId); + Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clustedId); + boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clustedId); + boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clustedId); + + int minMemory = (int)(newServiceOffering.getRamSize() / (divideMemoryByOverprovisioning ? memoryOvercommitRatio : 1)); + int minSpeed = (int)(newServiceOffering.getSpeed() / (divideCpuByOverprovisioning ? cpuOvercommitRatio : 1)); + + ScaleVmCommand scaleVmCommand = + new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, + newServiceOffering.getSpeed(), minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, newServiceOffering.getLimitCpuUse()); + + scaleVmCommand.getVirtualMachine().setId(vm.getId()); + scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid()); + scaleVmCommand.getVirtualMachine().setType(vm.getType()); + + Long dstHostId = vm.getHostId(); + + if (vm.getHypervisorType().equals(HypervisorType.VMware)) { + HypervisorGuru hvGuru = hvGuruMgr.getGuru(vm.getHypervisorType()); + Map details = hvGuru.getClusterSettings(vm.getId()); + scaleVmCommand.getVirtualMachine().setDetails(details); + } + + ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), vmScaleReconfigurationActions.getNodeId(), State.Running, vm.getType(), vm.getId()); + + work.setStep(Step.Prepare); + work.setResourceType(ItWorkVO.ResourceType.Host); + work.setResourceId(vm.getHostId()); + workDao.persist(work); + + try { + Answer reconfigureAnswer = agentMgr.send(vm.getHostId(), scaleVmCommand); + + if (reconfigureAnswer == null || !reconfigureAnswer.getResult()) { + logger.error("Unable to scale vm due to {}", (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); + throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); + } + + vmServiceOfferingUpgradeManager.upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + + if (vm.getType().equals(VirtualMachine.Type.User)) { + userVmMgr.generateUsageEvent(vm, vm.isDisplayVm(), EventTypes.EVENT_VM_DYNAMIC_SCALE); + } + + if (reconfiguringOnExistingHost) { + vm.setServiceOfferingId(oldServiceOffering.getId()); + capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); + vm.setServiceOfferingId(newServiceOffering.getId()); + capacityMgr.allocateVmCapacity(vm, false); + } + + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException("Operation timed out on reconfiguring " + vm, dstHostId); + } catch (final AgentUnavailableException e) { + throw e; + } + + return vm; + } + + void removeCustomOfferingDetails(long vmId) { + vmServiceOfferingUpgradeManager.removeCustomOfferingDetails(vmId); + } + + void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering) { + vmServiceOfferingUpgradeManager.saveCustomOfferingDetails(vmId, serviceOffering); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmServiceOfferingUpgradeManager.java b/engine/orchestration/src/main/java/com/cloud/vm/VmServiceOfferingUpgradeManager.java new file mode 100644 index 000000000000..6a5328318834 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmServiceOfferingUpgradeManager.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; + +/** + * Service-offering upgrade helpers - checking whether a VM can move to + * a new {@link ServiceOffering}, applying the offering to the VM row, + * updating the customizable dynamic-offering detail map, and reporting + * whether the VM's ROOT volume currently sits on local storage. + * + *

Extracted from {@link VirtualMachineManagerImpl} as part of the + * Phase 4 Spring-component decomposition. {@code VirtualMachineManagerImpl} + * retains one-line wrappers for compatibility. + */ +public interface VmServiceOfferingUpgradeManager { + + void checkIfCanUpgrade(VirtualMachine vmInstance, ServiceOffering newServiceOffering); + + void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, DiskOffering newDiskOffering); + + /** + * Returns {@code true} when the VM's ROOT volume is allocated on a + * pool whose scope is {@link com.cloud.storage.ScopeType#HOST} + * (i.e. local storage). A VM with no ROOT volume yet is treated as + * shared (returns {@code false}). + */ + boolean isRootVolumeOnLocalStorage(long vmId); + + /** + * Update the VM row to reference {@code newServiceOffering}, + * mirroring its HA / cpu-limit / dynamically-scalable flags, + * persisting the customizable detail trio + * (cpuNumber/cpuSpeed/memory) when the new offering is dynamic, + * and clearing those details when the previous offering was + * dynamic but the new one is not. + */ + boolean upgradeVmDb(long vmId, ServiceOffering newServiceOffering, + ServiceOffering currentServiceOffering); + + /** + * Strip the customizable dynamic-offering detail trio + * (cpuNumber/cpuSpeed/memory) from the VM detail map while + * preserving every other detail row. Used when moving a VM from a + * dynamic offering to a static one. + */ + void removeCustomOfferingDetails(long vmId); + + /** + * Persist the customizable dynamic-offering detail trio + * (cpuNumber/cpuSpeed/memory) for the VM, but only for fields the + * underlying offering itself leaves unfilled — this matches the + * VM-snapshot restore invariant in + * {@code UserVmManagerImpl.validateCustomParameters}, which rejects + * persisted details for non-customizable parameters. + */ + void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmServiceOfferingUpgradeManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmServiceOfferingUpgradeManagerImpl.java new file mode 100644 index 000000000000..d9ec13e27eb7 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmServiceOfferingUpgradeManagerImpl.java @@ -0,0 +1,218 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.event.UsageEventVO; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.ScopeType; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume.Type; +import com.cloud.storage.VolumeApiServiceImpl; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.StringUtils; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + +/** + * Service-offering upgrade persistence — extracted from + * {@link VirtualMachineManagerImpl}. + * + * @see VmServiceOfferingUpgradeManager + */ +@Component +public class VmServiceOfferingUpgradeManagerImpl implements VmServiceOfferingUpgradeManager { + + private static final Logger logger = LogManager.getLogger(VmServiceOfferingUpgradeManagerImpl.class); + + @Inject + private VolumeDao volumeDao; + @Inject + private PrimaryDataStoreDao storagePoolDao; + @Inject + private VMInstanceDao vmInstanceDao; + @Inject + private VMInstanceDetailsDao vmInstanceDetailsDao; + @Inject + private VMTemplateDao templateDao; + @Inject + private ServiceOfferingDao serviceOfferingDao; + @Inject + private DiskOfferingDao diskOfferingDao; + @Inject + private EntityManager entityMgr; + @Inject + private UserVmManager userVmManager; + + @Override + public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffering newServiceOffering) { + if (newServiceOffering == null) { + throw new InvalidParameterValueException("Invalid parameter, newServiceOffering can't be null"); + } + + if (ServiceOffering.State.Inactive.equals(newServiceOffering.getState())) { + throw new InvalidParameterValueException(String.format("New service offering is inactive: [%s].", newServiceOffering.getUuid())); + } + + if (!(vmInstance.getState().equals(State.Stopped) || vmInstance.getState().equals(State.Running))) { + logger.warn("Unable to upgrade virtual machine {} in state {}", vmInstance.toString(), vmInstance.getState()); + throw new InvalidParameterValueException("Unable to upgrade virtual machine " + vmInstance.toString() + " " + " in state " + + vmInstance.getState() + "; make sure the virtual machine is stopped/running"); + } + + if (!newServiceOffering.isDynamic() && vmInstance.getServiceOfferingId() == newServiceOffering.getId()) { + logger.info("Not upgrading vm {} since it already has the requested service offering ({})", vmInstance.toString(), newServiceOffering.getName()); + + throw new InvalidParameterValueException("Not upgrading vm " + vmInstance.toString() + " since it already " + + "has the requested service offering (" + newServiceOffering.getName() + ")"); + } + + final ServiceOfferingVO currentServiceOffering = serviceOfferingDao.findByIdIncludingRemoved(vmInstance.getId(), vmInstance.getServiceOfferingId()); + final DiskOfferingVO currentDiskOffering = diskOfferingDao.findByIdIncludingRemoved(currentServiceOffering.getDiskOfferingId()); + final DiskOfferingVO newDiskOffering = diskOfferingDao.findById(newServiceOffering.getDiskOfferingId()); + + checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstance, newDiskOffering); + + if (currentServiceOffering.isSystemUse() != newServiceOffering.isSystemUse()) { + throw new InvalidParameterValueException("isSystem property is different for current service offering and new service offering"); + } + + final List currentTags = StringUtils.csvTagsToList(currentDiskOffering.getTags()); + final List newTags = StringUtils.csvTagsToList(newDiskOffering.getTags()); + if (VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(vmInstance.getDataCenterId())) { + if (!VolumeApiServiceImpl.doesNewDiskOfferingHasTagsAsOldDiskOffering(currentDiskOffering, newDiskOffering)) { + throw new InvalidParameterValueException("Unable to upgrade virtual machine; the current service offering " + + " should have tags as subset of the new service offering tags. Current service offering tags: " + currentTags + "; " + + "new service offering tags: " + newTags); + } + } + } + + @Override + public void checkIfNewOfferingStorageScopeMatchesStoragePool(VirtualMachine vmInstance, DiskOffering newDiskOffering) { + boolean isRootVolumeOnLocalStorage = isRootVolumeOnLocalStorage(vmInstance.getId()); + + if (newDiskOffering.isUseLocalStorage() && !isRootVolumeOnLocalStorage) { + String message = String.format("Unable to upgrade virtual machine %s, target offering use local storage but the storage pool where " + + "the volume is allocated is a shared storage.", vmInstance.toString()); + throw new InvalidParameterValueException(message); + } + + if (!newDiskOffering.isUseLocalStorage() && isRootVolumeOnLocalStorage) { + String message = String.format("Unable to upgrade virtual machine %s, target offering use shared storage but the storage pool where " + + "the volume is allocated is a local storage.", vmInstance.toString()); + throw new InvalidParameterValueException(message); + } + } + + @Override + public boolean isRootVolumeOnLocalStorage(long vmId) { + ScopeType poolScope = ScopeType.ZONE; + List volumes = volumeDao.findByInstanceAndType(vmId, Type.ROOT); + if (CollectionUtils.isNotEmpty(volumes)) { + VolumeVO rootDisk = volumes.get(0); + Long poolId = rootDisk.getPoolId(); + if (poolId != null) { + StoragePoolVO storagePoolVO = storagePoolDao.findById(poolId); + poolScope = storagePoolVO.getScope(); + } + } + return ScopeType.HOST == poolScope; + } + + @Override + public boolean upgradeVmDb(final long vmId, final ServiceOffering newServiceOffering, + ServiceOffering currentServiceOffering) { + final VMInstanceVO vmForUpdate = vmInstanceDao.findById(vmId); + vmForUpdate.setServiceOfferingId(newServiceOffering.getId()); + final ServiceOffering newSvcOff = entityMgr.findById(ServiceOffering.class, newServiceOffering.getId()); + vmForUpdate.setHaEnabled(newSvcOff.isOfferHA()); + vmForUpdate.setLimitCpuUse(newSvcOff.getLimitCpuUse()); + vmForUpdate.setServiceOfferingId(newSvcOff.getId()); + if (newServiceOffering.isDynamic()) { + saveCustomOfferingDetails(vmId, newServiceOffering); + } + if (currentServiceOffering.isDynamic() && !newServiceOffering.isDynamic()) { + removeCustomOfferingDetails(vmId); + } + VMTemplateVO template = templateDao.findByIdIncludingRemoved(vmForUpdate.getTemplateId()); + boolean dynamicScalingEnabled = userVmManager.checkIfDynamicScalingCanBeEnabled(vmForUpdate, newServiceOffering, template, vmForUpdate.getDataCenterId()); + vmForUpdate.setDynamicallyScalable(dynamicScalingEnabled); + return vmInstanceDao.update(vmId, vmForUpdate); + } + + @Override + public void removeCustomOfferingDetails(long vmId) { + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); + details.remove(UsageEventVO.DynamicParameters.cpuNumber.name()); + details.remove(UsageEventVO.DynamicParameters.cpuSpeed.name()); + details.remove(UsageEventVO.DynamicParameters.memory.name()); + List detailList = new ArrayList<>(); + for (Map.Entry entry : details.entrySet()) { + VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); + detailList.add(detailVO); + } + vmInstanceDetailsDao.saveDetails(detailList); + } + + @Override + public void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering) { + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); + + // We need to restore only the customizable parameters. If we save a parameter that is not customizable and attempt + // to restore a VM snapshot, com.cloud.vm.UserVmManagerImpl.validateCustomParameters will fail. + ServiceOffering unfilledOffering = serviceOfferingDao.findByIdIncludingRemoved(serviceOffering.getId()); + if (unfilledOffering.getCpu() == null) { + details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), serviceOffering.getCpu().toString()); + } + if (unfilledOffering.getSpeed() == null) { + details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), serviceOffering.getSpeed().toString()); + } + if (unfilledOffering.getRamSize() == null) { + details.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString()); + } + + List detailList = new ArrayList<>(); + for (Map.Entry entry : details.entrySet()) { + VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); + detailList.add(detailVO); + } + vmInstanceDetailsDao.saveDetails(detailList); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStartProfilePreparationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStartProfilePreparationService.java new file mode 100644 index 000000000000..5490881c8951 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStartProfilePreparationService.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +public interface VmStartProfilePreparationService { + + void updateOverCommitRatioForVmProfile(VirtualMachineProfile vmProfile, long clusterId); + + void conditionallySetPodToDeployIn(VMInstanceVO vm); + + boolean areAllVolumesAllocated(long vmId); + + void logBootModeParameters(Map params); + + void resetVmNicsDeviceId(Long vmId); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStartProfilePreparationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStartProfilePreparationServiceImpl.java new file mode 100644 index 000000000000..ce1681a83892 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStartProfilePreparationServiceImpl.java @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterDetailsVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + +@Component +public class VmStartProfilePreparationServiceImpl implements VmStartProfilePreparationService { + + private static final Logger logger = LogManager.getLogger(VmStartProfilePreparationServiceImpl.class); + + @Inject + protected ClusterDetailsDao clusterDetailsDao; + @Inject + protected VMInstanceDetailsDao vmInstanceDetailsDao; + @Inject + protected VolumeDao volumeDao; + @Inject + protected NicDao nicsDao; + + @Override + public void updateOverCommitRatioForVmProfile(VirtualMachineProfile vmProfile, long clusterId) { + final ClusterDetailsVO clusterDetailCpu = clusterDetailsDao.findDetail(clusterId, VmDetailConstants.CPU_OVER_COMMIT_RATIO); + final ClusterDetailsVO clusterDetailRam = clusterDetailsDao.findDetail(clusterId, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); + final float parsedClusterCpuDetailCpu = Float.parseFloat(clusterDetailCpu.getValue()); + final float parsedClusterDetailRam = Float.parseFloat(clusterDetailRam.getValue()); + VMInstanceDetailVO vmDetailCpu = vmInstanceDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO); + VMInstanceDetailVO vmDetailRam = vmInstanceDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); + + if ((vmDetailCpu == null && parsedClusterCpuDetailCpu > 1f) || + (vmDetailCpu != null && Float.parseFloat(vmDetailCpu.getValue()) != parsedClusterCpuDetailCpu)) { + vmInstanceDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO, clusterDetailCpu.getValue(), true); + } + if ((vmDetailRam == null && parsedClusterDetailRam > 1f) || + (vmDetailRam != null && Float.parseFloat(vmDetailRam.getValue()) != parsedClusterDetailRam)) { + vmInstanceDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, clusterDetailRam.getValue(), true); + } + + vmProfile.setCpuOvercommitRatio(Float.parseFloat(clusterDetailCpu.getValue())); + vmProfile.setMemoryOvercommitRatio(Float.parseFloat(clusterDetailRam.getValue())); + } + + /** + * Setting pod id to null can result in migration of Volumes across pods. This is not desirable for VMs which + * have a volume in Ready state (happens when a VM is shutdown and started again). + * So, we set it to null only when + * migration of VM across cluster is enabled + * Or, volumes are still in allocated state for that VM (happens when VM is Starting/deployed for the first time) + */ + @Override + public void conditionallySetPodToDeployIn(VMInstanceVO vm) { + if (MIGRATE_VM_ACROSS_CLUSTERS.valueIn(vm.getDataCenterId()) || areAllVolumesAllocated(vm.getId())) { + vm.setPodIdToDeployIn(null); + } + } + + @Override + public boolean areAllVolumesAllocated(long vmId) { + final List vols = volumeDao.findByInstance(vmId); + return CollectionUtils.isEmpty(vols) || vols.stream().allMatch(v -> Volume.State.Allocated.equals(v.getState())); + } + + @Override + public void logBootModeParameters(Map params) { + if (params == null) { + return; + } + + StringBuilder msgBuf = new StringBuilder("Uefi params "); + boolean log = false; + if (params.get(VirtualMachineProfile.Param.UefiFlag) != null) { + msgBuf.append(String.format("UefiFlag: %s ", params.get(VirtualMachineProfile.Param.UefiFlag))); + log = true; + } + if (params.get(VirtualMachineProfile.Param.BootType) != null) { + msgBuf.append(String.format("Boot Type: %s ", params.get(VirtualMachineProfile.Param.BootType))); + log = true; + } + if (params.get(VirtualMachineProfile.Param.BootMode) != null) { + msgBuf.append(String.format("Boot Mode: %s ", params.get(VirtualMachineProfile.Param.BootMode))); + log = true; + } + if (params.get(VirtualMachineProfile.Param.BootIntoSetup) != null) { + msgBuf.append(String.format("Boot into Setup: %s ", params.get(VirtualMachineProfile.Param.BootIntoSetup))); + log = true; + } + if (params.get(VirtualMachineProfile.Param.ConsiderLastHost) != null) { + msgBuf.append(String.format("Consider last host: %s ", params.get(VirtualMachineProfile.Param.ConsiderLastHost))); + log = true; + } + if (log) { + logger.info(msgBuf.toString()); + } + } + + @Override + public void resetVmNicsDeviceId(Long vmId) { + final List nics = nicsDao.listByVmId(vmId); + nics.sort(Comparator.comparingInt(NicVO::getDeviceId)); + int deviceId = 0; + for (final NicVO nic : nics) { + if (nic.getDeviceId() != deviceId) { + nic.setDeviceId(deviceId); + nicsDao.update(nic.getId(), nic); + } + deviceId++; + } + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStateMachineActions.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStateMachineActions.java new file mode 100644 index 000000000000..6c3fac384188 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStateMachineActions.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.utils.fsm.NoTransitionException; + +/** + * Narrow callback interface allowing {@link VmPowerStateSyncManagerImpl} to invoke + * state-machine operations that must remain on the god class. + * Implemented by {@link VirtualMachineManagerImpl}. + * Injected with {@code @Lazy} to break the DI cycle. + */ +interface VmStateMachineActions { + + boolean stateTransitTo(VirtualMachine vm, VirtualMachine.Event event, Long hostId) throws NoTransitionException; + + boolean sendStop(VirtualMachineGuru guru, VirtualMachineProfile profile, boolean force, boolean checkBeforeCleanup); + + void releaseVmResources(VirtualMachineProfile profile, boolean forced); + + VirtualMachineGuru getVmGuru(VirtualMachine vm); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStatsCollector.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStatsCollector.java new file mode 100644 index 000000000000..4751e4370f6d --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStatsCollector.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.cloud.host.Host; + +/** + * Per-host VM statistics collector — issues the agent + * {@code GetVm*StatsCommand} family against a target host, parses the + * answer's name-keyed map, and re-keys the result by VM id so callers + * (the central {@code StatsCollector} background loop, the + * autoscale loop, etc.) see a uniform {@code Map} shape. + * + *

Extracted from {@link VirtualMachineManagerImpl} as part of the + * Phase 4 Spring-component decomposition. The public + * {@link VirtualMachineManager} interface retains the three + * {@code getVirtualMachineStatistics} / {@code getVmDiskStatistics} / + * {@code getVmNetworkStatistics} overloads as thin wrappers that + * delegate to this collector, so callers that already inject + * {@code VirtualMachineManager} keep compiling unchanged. + * + *

Every method swallows agent unavailability and answer-failure + * by returning an empty map — matching the legacy in-place behavior + * relied on by the StatsCollector's per-host loop. + */ +public interface VmStatsCollector { + + /** + * Convenience overload: resolve VM ids to instance names via + * {@link com.cloud.vm.dao.VMInstanceDao#getNameIdMapForVmIds}, + * then forward to {@link #getVirtualMachineStatistics(Host, Map)}. + * Returns an empty map when {@code vmIds} is empty. + */ + HashMap getVirtualMachineStatistics(Host host, List vmIds); + + /** + * Issue a {@code GetVmStatsCommand} for the supplied + * {@code instanceName -> vmId} map, then re-key the answer by id. + * Returns an empty map when {@code vmInstanceNameIdMap} is empty, + * the agent does not answer, the answer reports failure, or the + * answer's stats map is null. + */ + HashMap getVirtualMachineStatistics(Host host, Map vmInstanceNameIdMap); + + /** + * Same shape as {@link #getVirtualMachineStatistics(Host, Map)} but + * sends a {@code GetVmDiskStatsCommand}; each map entry is a list + * of per-disk statistics for that VM. + */ + HashMap> getVmDiskStatistics(Host host, Map vmInstanceNameIdMap); + + /** + * Same shape as {@link #getVirtualMachineStatistics(Host, Map)} but + * sends a {@code GetVmNetworkStatsCommand}; each map entry is a + * list of per-nic statistics for that VM. + */ + HashMap> getVmNetworkStatistics(Host host, Map vmInstanceNameIdMap); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStatsCollectorImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStatsCollectorImpl.java new file mode 100644 index 000000000000..82f8a10a78bc --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStatsCollectorImpl.java @@ -0,0 +1,140 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.GetVmDiskStatsAnswer; +import com.cloud.agent.api.GetVmDiskStatsCommand; +import com.cloud.agent.api.GetVmNetworkStatsAnswer; +import com.cloud.agent.api.GetVmNetworkStatsCommand; +import com.cloud.agent.api.GetVmStatsAnswer; +import com.cloud.agent.api.GetVmStatsCommand; +import com.cloud.agent.api.VmDiskStatsEntry; +import com.cloud.agent.api.VmNetworkStatsEntry; +import com.cloud.agent.api.VmStatsEntry; +import com.cloud.host.Host; +import com.cloud.vm.dao.VMInstanceDao; + +/** + * Per-host VM statistics collector — extracted from + * {@link VirtualMachineManagerImpl}. + * + * @see VmStatsCollector + */ +@Component +public class VmStatsCollectorImpl implements VmStatsCollector { + + private static final Logger logger = LogManager.getLogger(VmStatsCollectorImpl.class); + + @Inject + private AgentManager agentMgr; + @Inject + private VMInstanceDao vmInstanceDao; + + @Override + public HashMap getVirtualMachineStatistics(Host host, List vmIds) { + HashMap vmStatsById = new HashMap<>(); + if (CollectionUtils.isEmpty(vmIds)) { + return vmStatsById; + } + Map vmMap = vmInstanceDao.getNameIdMapForVmIds(vmIds); + return getVirtualMachineStatistics(host, vmMap); + } + + @Override + public HashMap getVirtualMachineStatistics(Host host, Map vmInstanceNameIdMap) { + HashMap vmStatsById = new HashMap<>(); + if (MapUtils.isEmpty(vmInstanceNameIdMap)) { + return vmStatsById; + } + Answer answer = agentMgr.easySend(host.getId(), new GetVmStatsCommand( + new ArrayList<>(vmInstanceNameIdMap.keySet()), host.getGuid(), host.getName())); + if (answer == null || !answer.getResult()) { + logger.warn("Unable to obtain VM statistics."); + return vmStatsById; + } + HashMap vmStatsByName = ((GetVmStatsAnswer) answer).getVmStatsMap(); + if (vmStatsByName == null) { + logger.warn("Unable to obtain VM statistics."); + return vmStatsById; + } + for (Map.Entry entry : vmStatsByName.entrySet()) { + vmStatsById.put(vmInstanceNameIdMap.get(entry.getKey()), entry.getValue()); + } + return vmStatsById; + } + + @Override + public HashMap> getVmDiskStatistics(Host host, Map vmInstanceNameIdMap) { + HashMap> vmDiskStatsById = new HashMap<>(); + if (MapUtils.isEmpty(vmInstanceNameIdMap)) { + return vmDiskStatsById; + } + Answer answer = agentMgr.easySend(host.getId(), new GetVmDiskStatsCommand( + new ArrayList<>(vmInstanceNameIdMap.keySet()), host.getGuid(), host.getName())); + if (answer == null || !answer.getResult()) { + logger.warn("Unable to obtain VM disk statistics."); + return vmDiskStatsById; + } + HashMap> vmDiskStatsByName = ((GetVmDiskStatsAnswer) answer).getVmDiskStatsMap(); + if (vmDiskStatsByName == null) { + logger.warn("Unable to obtain VM disk statistics."); + return vmDiskStatsById; + } + for (Map.Entry> entry : vmDiskStatsByName.entrySet()) { + vmDiskStatsById.put(vmInstanceNameIdMap.get(entry.getKey()), entry.getValue()); + } + return vmDiskStatsById; + } + + @Override + public HashMap> getVmNetworkStatistics(Host host, Map vmInstanceNameIdMap) { + HashMap> vmNetworkStatsById = new HashMap<>(); + if (MapUtils.isEmpty(vmInstanceNameIdMap)) { + return vmNetworkStatsById; + } + Answer answer = agentMgr.easySend(host.getId(), new GetVmNetworkStatsCommand( + new ArrayList<>(vmInstanceNameIdMap.keySet()), host.getGuid(), host.getName())); + if (answer == null || !answer.getResult()) { + logger.warn("Unable to obtain VM network statistics."); + return vmNetworkStatsById; + } + HashMap> vmNetworkStatsByName = ((GetVmNetworkStatsAnswer) answer).getVmNetworkStatsMap(); + if (vmNetworkStatsByName == null) { + logger.warn("Unable to obtain VM network statistics."); + return vmNetworkStatsById; + } + for (Map.Entry> entry : vmNetworkStatsByName.entrySet()) { + vmNetworkStatsById.put(vmInstanceNameIdMap.get(entry.getKey()), entry.getValue()); + } + return vmNetworkStatsById; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStopCommandService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStopCommandService.java new file mode 100644 index 000000000000..0acb6e0ef372 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStopCommandService.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.DpdkTO; + +public interface VmStopCommandService { + + void decorateStopCommandWithNetworkDetails(StopCommand command, VirtualMachine vm); + + StopCommand buildCleanupCommand(VirtualMachine vm, boolean executeInSequence, Map dpdkInterfaceMapping); + + StopCommand buildCleanupCommand(String vmName, boolean executeInSequence); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStopCommandServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStopCommandServiceImpl.java new file mode 100644 index 000000000000..ef5c4cf6e220 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStopCommandServiceImpl.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.MapUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.DpdkTO; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmStopCommandServiceImpl implements VmStopCommandService { + + private static final Logger logger = LogManager.getLogger(VmStopCommandServiceImpl.class); + + @Inject + protected NicDao nicsDao; + @Inject + protected VMInstanceDao vmDao; + @Inject + protected VmVlanPersistenceMappingService vmVlanPersistenceMappingService; + + @Override + public void decorateStopCommandWithNetworkDetails(StopCommand command, VirtualMachine vm) { + command.setControlIp(getControlNicIpForVM(vm)); + Map vlanToPersistenceMap = vmVlanPersistenceMappingService.getVlanToPersistenceMapForVM(vm.getId()); + if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { + command.setVlanToPersistenceMap(vlanToPersistenceMap); + } + } + + @Override + public StopCommand buildCleanupCommand(VirtualMachine vm, boolean executeInSequence, Map dpdkInterfaceMapping) { + StopCommand command = new StopCommand(vm, executeInSequence, false); + decorateStopCommandWithNetworkDetails(command, vm); + if (MapUtils.isNotEmpty(dpdkInterfaceMapping)) { + command.setDpdkInterfaceMapping(dpdkInterfaceMapping); + } + return command; + } + + @Override + public StopCommand buildCleanupCommand(String vmName, boolean executeInSequence) { + VirtualMachine vm = vmDao.findVMByInstanceName(vmName); + StopCommand command = new StopCommand(vmName, executeInSequence, false); + decorateStopCommandWithNetworkDetails(command, vm); + return command; + } + + private String getControlNicIpForVM(VirtualMachine vm) { + if (null == vm.getType()) { + return null; + } + + switch (vm.getType()) { + case ConsoleProxy: + case SecondaryStorageVm: + NicVO nic = nicsDao.getControlNicForVM(vm.getId()); + return nic.getIPv4Address(); + case DomainRouter: + return vm.getPrivateIpAddress(); + default: + logger.debug("{} is a [{}], returning null for control Nic IP.", vm.toString(), vm.getType()); + return null; + } + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStopOrchestrationService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStopOrchestrationService.java new file mode 100644 index 000000000000..3a1e46d334ff --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStopOrchestrationService.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.vm.VirtualMachine.Event; + +public interface VmStopOrchestrationService { + + boolean sendStop(VirtualMachineGuru guru, VirtualMachineProfile profile, boolean force, boolean checkBeforeCleanup); + + boolean cleanup(VirtualMachineGuru guru, VirtualMachineProfile profile, ItWorkVO work, Event event, boolean cleanUpEvenIfUnableToStop); + + void releaseVmResources(VirtualMachineProfile profile, boolean forced); + + void advanceStop(String vmUuid, boolean cleanUpEvenIfUnableToStop) + throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException; + + void orchestrateStop(String vmUuid, boolean cleanUpEvenIfUnableToStop) + throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmStopOrchestrationServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmStopOrchestrationServiceImpl.java new file mode 100644 index 000000000000..6d8b4144a908 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmStopOrchestrationServiceImpl.java @@ -0,0 +1,511 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static com.cloud.vm.VirtualMachineManager.ResourceCountRunningVMsonly; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.api.ApiDBUtils; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.Status; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ResourceState; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.user.ResourceLimitService; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithException; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.ItWorkVO.Step; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmStopOrchestrationServiceImpl implements VmStopOrchestrationService { + + private static final Logger logger = LogManager.getLogger(VmStopOrchestrationServiceImpl.class); + + @Inject + protected AgentManager agentMgr; + @Inject + protected VMInstanceDao vmDao; + @Inject + protected HostDao hostDao; + @Inject + protected ItWorkDao workDao; + @Inject + protected UserVmDao userVmDao; + @Inject + protected VolumeDao volsDao; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + @Inject + protected ServiceOfferingDao offeringDao; + @Inject + protected VMTemplateDao templateDao; + @Inject + protected ResourceManager resourceMgr; + @Inject + protected ResourceLimitService resourceLimitMgr; + @Inject + protected VolumeOrchestrationService volumeMgr; + @Inject + protected NetworkOrchestrationService networkMgr; + @Inject + protected VmWorkJobQueueService vmWorkJobQueueService; + @Inject + protected VmStopCommandService vmStopCommandService; + @Inject + @Lazy + protected VirtualMachineManagerImpl virtualMachineManager; + + private List> getVolumesToDisconnect(VirtualMachine vm) { + List> volumesToDisconnect = new ArrayList<>(); + + List volumes = volsDao.findByInstance(vm.getId()); + + if (CollectionUtils.isEmpty(volumes)) { + return volumesToDisconnect; + } + + for (VolumeVO volume : volumes) { + StoragePoolVO storagePool = storagePoolDao.findById(volume.getPoolId()); + + if (storagePool != null && storagePool.isManaged()) { + Map info = new HashMap<>(); + + info.put(DiskTO.STORAGE_HOST, storagePool.getHostAddress()); + info.put(DiskTO.STORAGE_PORT, String.valueOf(storagePool.getPort())); + info.put(DiskTO.IQN, volume.get_iScsiName()); + info.put(DiskTO.PROTOCOL_TYPE, (volume.getPoolType() != null) ? volume.getPoolType().toString() : null); + + volumesToDisconnect.add(info); + } + } + + return volumesToDisconnect; + } + + @Override + public boolean sendStop(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final boolean force, final boolean checkBeforeCleanup) { + final VirtualMachine vm = profile.getVirtualMachine(); + StopCommand stpCmd = new StopCommand(vm, virtualMachineManager.getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup); + virtualMachineManager.updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stpCmd); + vmStopCommandService.decorateStopCommandWithNetworkDetails(stpCmd, vm); + stpCmd.setVolumesToDisconnect(getVolumesToDisconnect(vm)); + final StopCommand stop = stpCmd; + try { + Answer answer = null; + if(vm.getHostId() != null) { + answer = agentMgr.send(vm.getHostId(), stop); + } + if (answer != null && answer instanceof StopAnswer) { + final StopAnswer stopAns = (StopAnswer)answer; + if (vm.getType() == VirtualMachine.Type.User) { + final String platform = stopAns.getPlatform(); + if (platform != null) { + final UserVmVO userVm = userVmDao.findById(vm.getId()); + userVmDao.loadDetails(userVm); + userVm.setDetail(VmDetailConstants.PLATFORM, platform); + userVmDao.saveDetails(userVm); + } + } + + final GPUDeviceTO gpuDevice = stop.getGpuDevice(); + resourceMgr.updateGPUDetailsForVmStop(vm, gpuDevice); + if (!answer.getResult()) { + final String details = answer.getDetails(); + logger.debug("Unable to stop VM due to {}", details); + return false; + } + + guru.finalizeStop(profile, answer); + + final UserVmVO userVm = userVmDao.findById(vm.getId()); + if (vm.getType() == VirtualMachine.Type.User) { + if (userVm != null) { + userVm.setPowerState(PowerState.PowerOff); + userVmDao.update(userVm.getId(), userVm); + } + } + } else { + logger.error("Invalid answer received in response to a StopCommand for {}", vm.getInstanceName()); + return false; + } + + } catch (final AgentUnavailableException | OperationTimedoutException e) { + logger.warn("Unable to stop {} due to [{}].", vm.toString(), e.getMessage(), e); + if (!force) { + return false; + } + } + + return true; + } + + @Override + public boolean cleanup(final VirtualMachineGuru guru, final VirtualMachineProfile profile, final ItWorkVO work, final Event event, final boolean cleanUpEvenIfUnableToStop) { + final VirtualMachine vm = profile.getVirtualMachine(); + final State state = vm.getState(); + logger.debug("Cleaning up resources for the vm {} in {} state", vm, state); + try { + if (state == State.Starting) { + if (work != null) { + final Step step = work.getStep(); + if (step == Step.Starting && !cleanUpEvenIfUnableToStop) { + logger.warn("Unable to cleanup vm {}; work state is incorrect: {}", vm, step); + return false; + } + + if (step == Step.Started || step == Step.Starting || step == Step.Release) { + if (vm.getHostId() != null) { + if (!virtualMachineManager.sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { + logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Starting); + return false; + } + } + } + + if (step != Step.Release && step != Step.Prepare && step != Step.Started && step != Step.Starting) { + logger.debug("Cleanup is not needed for vm {}; work state is incorrect: {}", vm, step); + return true; + } + } else { + if (vm.getHostId() != null) { + if (!virtualMachineManager.sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { + logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Starting); + return false; + } + } + } + + } else if (state == State.Stopping) { + if (vm.getHostId() != null) { + if (!virtualMachineManager.sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { + logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Stopping); + return false; + } + } + } else if (state == State.Migrating) { + if (vm.getHostId() != null || vm.getLastHostId() != null) { + if (!virtualMachineManager.sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { + logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Migrating); + return false; + } + } + } else if (state == State.Running) { + if (!virtualMachineManager.sendStop(guru, profile, cleanUpEvenIfUnableToStop, false)) { + logger.warn("Failed to stop vm {} in {} state as a part of cleanup process", vm, State.Running); + return false; + } + } + } finally { + releaseVmResources(profile, cleanUpEvenIfUnableToStop); + } + + return true; + } + + @Override + public void releaseVmResources(final VirtualMachineProfile profile, final boolean forced) { + final VirtualMachine vm = profile.getVirtualMachine(); + final State state = vm.getState(); + try { + networkMgr.release(profile, forced); + logger.debug("Successfully released network resources for the VM {} in {} state", vm, state); + } catch (final Exception e) { + logger.warn("Unable to release some network resources for the VM {} in {} state", vm, state, e); + } + + try { + if (vm.getHypervisorType() != HypervisorType.BareMetal && vm.getHypervisorType() != HypervisorType.External) { + volumeMgr.release(profile); + logger.debug("Successfully released storage resources for the VM {} in {} state", vm, state); + } + } catch (final Exception e) { + logger.warn("Unable to release storage resources for the VM {} in {} state", vm, state, e); + } + + logger.debug("Successfully cleaned up resources for the VM {} in {} state", vm, state); + } + + @Override + public void advanceStop(final String vmUuid, final boolean cleanUpEvenIfUnableToStop) + throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + + final AsyncJobExecutionContext jobContext = AsyncJobExecutionContext.getCurrentExecutionContext(); + if (jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)) { + + VmWorkJobVO placeHolder = null; + final VirtualMachine vm = vmDao.findByUuid(vmUuid); + placeHolder = vmWorkJobQueueService.createPlaceHolderWork(vm.getId()); + try { + orchestrateStop(vmUuid, cleanUpEvenIfUnableToStop); + } finally { + vmWorkJobQueueService.expungePlaceHolderWork(placeHolder); + } + + } else { + final Outcome outcome = vmWorkJobQueueService.stopVmThroughJobQueue(vmUuid, cleanUpEvenIfUnableToStop); + + vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, vmUuid, "stopVm"); + + try { + vmWorkJobQueueService.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + throw new RuntimeException("Unexpected exception", ex); + } + } + } + + @Override + public void orchestrateStop(final String vmUuid, final boolean cleanUpEvenIfUnableToStop) throws AgentUnavailableException, OperationTimedoutException, ConcurrentOperationException { + final VMInstanceVO vm = vmDao.findByUuid(vmUuid); + + advanceStop(vm, cleanUpEvenIfUnableToStop); + } + + private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnableToStop) throws AgentUnavailableException, OperationTimedoutException, + ConcurrentOperationException { + final State state = vm.getState(); + if (state == State.Stopped) { + logger.debug("VM is already stopped: {}", vm); + return; + } + + if (state == State.Destroyed || state == State.Expunging || state == State.Error) { + logger.debug("Stopped called on {} but the state is {}", vm, state); + return; + } + + final ItWorkVO work = workDao.findByOutstandingWork(vm.getId(), vm.getState()); + if (work != null) { + logger.debug("Found an outstanding work item for this vm {} with state: {}, work id: {}", vm, vm.getState(), work.getId()); + } + final Long hostId = vm.getHostId(); + if (hostId == null) { + if (!cleanUpEvenIfUnableToStop) { + logger.debug("HostId is null but this is not a forced stop, cannot stop vm {} with state: {}", vm, vm.getState()); + throw new CloudRuntimeException("Unable to stop " + vm); + } + try { + virtualMachineManager.stateTransitTo(vm, Event.AgentReportStopped, null, null); + } catch (final NoTransitionException e) { + logger.warn(e.getMessage()); + } + + if (work != null) { + logger.debug("Updating work item to Done, id: {}", work.getId()); + work.setStep(Step.Done); + workDao.update(work.getId(), work); + } + return; + } else { + HostVO host = hostDao.findById(hostId); + if (!cleanUpEvenIfUnableToStop && vm.getState() == State.Running && host.getResourceState() == ResourceState.PrepareForMaintenance) { + logger.debug("Host is in PrepareForMaintenance state - Stop VM operation on the VM: {} is not allowed", vm); + throw new CloudRuntimeException(String.format("Stop VM operation on the VM %s is not allowed as host is preparing for maintenance mode", vm)); + } + } + + final VirtualMachineGuru vmGuru = virtualMachineManager.getVmGuru(vm); + final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); + + try { + if (!virtualMachineManager.stateTransitTo(vm, Event.StopRequested, vm.getHostId())) { + throw new ConcurrentOperationException(String.format("%s is being operated on.", vm.toString())); + } + } catch (final NoTransitionException e1) { + if (!cleanUpEvenIfUnableToStop) { + throw new CloudRuntimeException("We cannot stop " + vm + " when it is in state " + vm.getState()); + } + final boolean doCleanup = true; + logger.warn("Unable to transition the state but we're moving on because it's forced stop", e1); + + if (doCleanup) { + if (virtualMachineManager.cleanup(vmGuru, new VirtualMachineProfileImpl(vm), work, Event.StopRequested, cleanUpEvenIfUnableToStop)) { + try { + if (work != null) { + logger.debug("Updating work item to Done, id: {}", work.getId()); + } + if (!virtualMachineManager.changeState(vm, Event.AgentReportStopped, null, work, Step.Done)) { + throw new CloudRuntimeException("Unable to stop " + vm); + } + + } catch (final NoTransitionException e) { + logger.warn("Unable to cleanup {}", vm); + throw new CloudRuntimeException("Unable to stop " + vm, e); + } + } else { + logger.debug("Failed to cleanup VM: {}", vm); + throw new CloudRuntimeException("Failed to cleanup " + vm + " , current state " + vm.getState()); + } + } + } + + if (vm.getState() != State.Stopping) { + throw new CloudRuntimeException("We cannot proceed with stop VM " + vm + " since it is not in 'Stopping' state, current state: " + vm.getState()); + } + + vmGuru.prepareStop(profile); + + final StopCommand stop = new StopCommand(vm, virtualMachineManager.getExecuteInSequence(vm.getHypervisorType()), false, cleanUpEvenIfUnableToStop); + virtualMachineManager.updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stop); + vmStopCommandService.decorateStopCommandWithNetworkDetails(stop, vm); + + boolean stopped = false; + Answer answer = null; + try { + answer = agentMgr.send(vm.getHostId(), stop); + if (answer != null) { + if (answer instanceof StopAnswer) { + final StopAnswer stopAns = (StopAnswer)answer; + if (vm.getType() == VirtualMachine.Type.User) { + final String platform = stopAns.getPlatform(); + if (platform != null) { + final UserVmVO userVm = userVmDao.findById(vm.getId()); + userVmDao.loadDetails(userVm); + userVm.setDetail(VmDetailConstants.PLATFORM, platform); + userVmDao.saveDetails(userVm); + } + } + } + stopped = answer.getResult(); + if (!stopped) { + throw new CloudRuntimeException("Unable to stop the Instance due to " + answer.getDetails()); + } + vmGuru.finalizeStop(profile, answer); + final GPUDeviceTO gpuDevice = stop.getGpuDevice(); + resourceMgr.updateGPUDetailsForVmStop(vm, gpuDevice); + } else { + throw new CloudRuntimeException("Invalid answer received in response to a StopCommand on " + vm.instanceName); + } + + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.warn("Unable to stop {} due to [{}].", profile.toString(), e.toString(), e); + } finally { + if (!stopped) { + if (!cleanUpEvenIfUnableToStop) { + logger.warn("Unable to stop vm {}", vm); + try { + virtualMachineManager.stateTransitTo(vm, Event.OperationFailed, vm.getHostId()); + } catch (final NoTransitionException e) { + logger.warn("Unable to transition the state " + vm, e); + } + throw new CloudRuntimeException("Unable to stop " + vm); + } else { + logger.warn("Unable to actually stop {} but continue with release because it's a force stop", vm); + vmGuru.finalizeStop(profile, answer); + if (HypervisorType.External.equals(profile.getHypervisorType())) { + try { + virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.OperationSucceeded, null); + } catch (final NoTransitionException e) { + logger.warn("Unable to transition the state " + vm, e); + } + } + + } + } else { + if (VirtualMachine.systemVMs.contains(vm.getType())) { + HostVO systemVmHost = ApiDBUtils.findHostByTypeNameAndZoneId(vm.getDataCenterId(), vm.getHostName(), + VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType()) ? Host.Type.SecondaryStorageVM : Host.Type.ConsoleProxy); + if (systemVmHost != null) { + agentMgr.agentStatusTransitTo(systemVmHost, Status.Event.ShutdownRequested, virtualMachineManager.getNodeId()); + } + } + } + } + + logger.debug("{} is stopped on the host. Proceeding to release resource held.", vm); + + releaseVmResources(profile, cleanUpEvenIfUnableToStop); + + try { + if (work != null) { + logger.debug("Updating the outstanding work item to Done, id: {}", work.getId()); + work.setStep(Step.Done); + workDao.update(work.getId(), work); + } + + boolean result = Transaction.execute(new TransactionCallbackWithException() { + @Override + public Boolean doInTransaction(TransactionStatus status) throws NoTransitionException { + boolean result = virtualMachineManager.stateTransitTo(vm, Event.OperationSucceeded, null); + + if (result && VirtualMachine.Type.User.equals(vm.type) && ResourceCountRunningVMsonly.value()) { + ServiceOfferingVO offering = offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); + VMTemplateVO template = templateDao.findByIdIncludingRemoved(vm.getTemplateId()); + resourceLimitMgr.decrementVmResourceCount(vm.getAccountId(), vm.isDisplay(), offering, template); + } + return result; + } + }); + + if (!result) { + throw new CloudRuntimeException("unable to stop " + vm); + } + } catch (final NoTransitionException e) { + String message = String.format("Unable to stop %s due to [%s].", vm.toString(), e.getMessage()); + logger.warn(message, e); + throw new CloudRuntimeException(message, e); + } + } + +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmVlanPersistenceMappingService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmVlanPersistenceMappingService.java new file mode 100644 index 000000000000..31677ce4b866 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmVlanPersistenceMappingService.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +public interface VmVlanPersistenceMappingService { + + Map getVlanToPersistenceMapForVM(long vmId); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmVlanPersistenceMappingServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmVlanPersistenceMappingServiceImpl.java new file mode 100644 index 000000000000..824d9bf5bdd6 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmVlanPersistenceMappingServiceImpl.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.net.URI; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.springframework.stereotype.Component; + +import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.network.Network; +import com.cloud.network.Networks; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.utils.Pair; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmVlanPersistenceMappingServiceImpl implements VmVlanPersistenceMappingService { + + @Inject + protected UserVmJoinDao userVmJoinDao; + @Inject + protected DomainRouterJoinDao domainRouterJoinDao; + @Inject + protected NetworkDao networkDao; + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected VMInstanceDao vmDao; + + @Override + public Map getVlanToPersistenceMapForVM(long vmId) { + List userVmJoinVOs = userVmJoinDao.searchByIds(vmId); + Map vlanToPersistenceMap = new HashMap<>(); + if (CollectionUtils.isNotEmpty(userVmJoinVOs)) { + for (UserVmJoinVO userVmJoinVO : userVmJoinVOs) { + NetworkVO networkVO = networkDao.findById(userVmJoinVO.getNetworkId()); + updatePersistenceMap(vlanToPersistenceMap, networkVO); + } + } else { + VMInstanceVO vmInstanceVO = vmDao.findById(vmId); + if (vmInstanceVO != null && vmInstanceVO.getType() == VirtualMachine.Type.DomainRouter) { + DomainRouterJoinVO routerVO = domainRouterJoinDao.findById(vmId); + if (routerVO != null) { + NetworkVO networkVO = networkDao.findById(routerVO.getNetworkId()); + updatePersistenceMap(vlanToPersistenceMap, networkVO); + } + } + } + return vlanToPersistenceMap; + } + + private void updatePersistenceMap(Map vlanToPersistenceMap, NetworkVO networkVO) { + if (networkVO == null) { + return; + } + NetworkOfferingVO offeringVO = networkOfferingDao.findById(networkVO.getNetworkOfferingId()); + if (offeringVO == null) { + return; + } + Pair data = getVMNetworkDetails(networkVO, offeringVO.isPersistent()); + Boolean shouldDeleteNwResource = (MapUtils.isNotEmpty(vlanToPersistenceMap) && data != null) ? vlanToPersistenceMap.get(data.first()) : null; + if (data != null && (shouldDeleteNwResource == null || shouldDeleteNwResource)) { + vlanToPersistenceMap.put(data.first(), data.second()); + } + } + + /** + * + * @param networkVO - the network object used to determine the vlanId from the broadcast URI + * @param isPersistent - indicates if the corresponding network's network offering is Persistent + * + * @return - basically returns the vlan ID which is used to determine the + * bridge name for KVM hypervisor and based on the network and isolation type and persistent setting of the offering + * we decide whether the bridge is to be deleted (KVM) if the last VM in that host is destroyed / migrated + */ + private Pair getVMNetworkDetails(NetworkVO networkVO, boolean isPersistent) { + URI broadcastUri = networkVO.getBroadcastUri(); + if (broadcastUri != null) { + String scheme = broadcastUri.getScheme(); + String vlanId = Networks.BroadcastDomainType.getValue(broadcastUri); + boolean shouldDelete = !((networkVO.getGuestType() == Network.GuestType.L2 || networkVO.getGuestType() == Network.GuestType.Isolated) && + (scheme != null && scheme.equalsIgnoreCase("vlan")) + && isPersistent); + if (shouldDelete) { + int persistentNetworksCount = networkDao.getOtherPersistentNetworksCount(networkVO.getId(), networkVO.getBroadcastUri().toString(), true); + if (persistentNetworksCount > 0) { + shouldDelete = false; + } + } + return new Pair<>(vlanId, shouldDelete); + } + return null; + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmVolumeMigrationPlanningService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmVolumeMigrationPlanningService.java new file mode 100644 index 000000000000..50987a8d0cc2 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmVolumeMigrationPlanningService.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.Map; + +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.host.Host; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; + +/** + * Plans volume-to-storage-pool mappings when migrating a VM. + * + *

Extracted from {@link VirtualMachineManagerImpl} as part of Phase 4 decomposition (slice 5).

+ */ +public interface VmVolumeMigrationPlanningService { + + /** + * Builds the complete mapping of VM volumes to target storage pools for a live migration. + * User-supplied mappings are honoured first; any remaining volumes are auto-mapped using + * the allocator chain. + * + * @param profile the VM profile being migrated + * @param targetHost the destination host + * @param userDefinedMapOfVolumesAndStoragePools caller-supplied volume→pool overrides (may be empty) + * @return complete volume → storage-pool mapping for all VM volumes + */ + Map createMappingVolumeAndStoragePool(VirtualMachineProfile profile, Host targetHost, + Map userDefinedMapOfVolumesAndStoragePools); + + Map createMappingVolumeAndStoragePool(VirtualMachineProfile profile, DataCenterDeployment plan, + Map userDefinedMapOfVolumesAndStoragePools); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmVolumeMigrationPlanningServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmVolumeMigrationPlanningServiceImpl.java new file mode 100644 index 000000000000..6ebe3ba528ed --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmVolumeMigrationPlanningServiceImpl.java @@ -0,0 +1,290 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.springframework.stereotype.Component; + +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.host.Host; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.org.Cluster; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; + +/** + * Default implementation of {@link VmVolumeMigrationPlanningService}. + * + *

Extracted from {@link VirtualMachineManagerImpl} as part of Phase 4 decomposition (slice 5).

+ */ +@Component +public class VmVolumeMigrationPlanningServiceImpl implements VmVolumeMigrationPlanningService { + + @Inject + protected VolumeDao volumeDao; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + @Inject + protected StoragePoolHostDao poolHostDao; + @Inject + protected HostDao hostDao; + @Inject + protected ClusterDao clusterDao; + @Inject + protected DiskOfferingDao diskOfferingDao; + + private List storagePoolAllocators; + + @Inject + public void setStoragePoolAllocators(final List storagePoolAllocators) { + this.storagePoolAllocators = storagePoolAllocators; + } + + public List getStoragePoolAllocators() { + return storagePoolAllocators; + } + + // ------------------------------------------------------------------------- + // Public interface method + // ------------------------------------------------------------------------- + + /** + * We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user. + * If the user did not enter a complete mapping, the volumes that were left behind will be auto mapped using + * {@link #createStoragePoolMappingsForVolumes(VirtualMachineProfile, DataCenterDeployment, Map, List)} + */ + @Override + public Map createMappingVolumeAndStoragePool(VirtualMachineProfile profile, Host targetHost, + Map userDefinedMapOfVolumesAndStoragePools) { + return createMappingVolumeAndStoragePool(profile, + new DataCenterDeployment(targetHost.getDataCenterId(), targetHost.getPodId(), targetHost.getClusterId(), targetHost.getId(), null, null), + userDefinedMapOfVolumesAndStoragePools); + } + + @Override + public Map createMappingVolumeAndStoragePool(final VirtualMachineProfile profile, final DataCenterDeployment plan, + final Map userDefinedMapOfVolumesAndStoragePools) { + Host targetHost = null; + if (plan.getHostId() != null) { + targetHost = hostDao.findById(plan.getHostId()); + } + Map volumeToPoolObjectMap = buildMapUsingUserInformation(profile, targetHost, userDefinedMapOfVolumesAndStoragePools); + + List volumesNotMapped = findVolumesThatWereNotMappedByTheUser(profile, volumeToPoolObjectMap); + createStoragePoolMappingsForVolumes(profile, plan, volumeToPoolObjectMap, volumesNotMapped); + return volumeToPoolObjectMap; + } + + /** + * Given the map of volume to target storage pool entered by the user, we check for other volumes that the VM might have and were not configured. + * This map can be then used by CloudStack to find new target storage pools according to the target host. + */ + protected List findVolumesThatWereNotMappedByTheUser(VirtualMachineProfile profile, Map volumeToStoragePoolObjectMap) { + List allVolumes = volumeDao.findUsableVolumesForInstance(profile.getId()); + List volumesNotMapped = new ArrayList<>(); + for (Volume volume : allVolumes) { + if (!volumeToStoragePoolObjectMap.containsKey(volume)) { + volumesNotMapped.add(volume); + } + } + return volumesNotMapped; + } + + /** + * Builds the map of storage pools and volumes with the information entered by the user. Before creating the an entry we validate if the migration is feasible checking if the migration is allowed and if the target host can access the defined target storage pool. + */ + protected Map buildMapUsingUserInformation(VirtualMachineProfile profile, Host targetHost, Map userDefinedVolumeToStoragePoolMap) { + Map volumeToPoolObjectMap = new HashMap<>(); + if (MapUtils.isEmpty(userDefinedVolumeToStoragePoolMap)) { + return volumeToPoolObjectMap; + } + for (Long volumeId : userDefinedVolumeToStoragePoolMap.keySet()) { + VolumeVO volume = volumeDao.findById(volumeId); + + Long poolId = userDefinedVolumeToStoragePoolMap.get(volumeId); + StoragePoolVO targetPool = storagePoolDao.findById(poolId); + StoragePoolVO currentPool = storagePoolDao.findById(volume.getPoolId()); + + executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPool, volume, targetPool); + if (targetHost != null && poolHostDao.findByPoolHost(targetPool.getId(), targetHost.getId()) == null) { + throw new CloudRuntimeException( + String.format("Cannot migrate the volume [%s] to the storage pool [%s] while migrating VM [%s] to target host [%s]. The host does not have access to the storage pool entered.", + volume.getUuid(), targetPool.getUuid(), profile.getUuid(), targetHost.getUuid())); + } + if (currentPool.getId() == targetPool.getId()) { + logger.info("The volume [{}] is already allocated in storage pool [{}].", volume.getUuid(), targetPool.getUuid()); + } + volumeToPoolObjectMap.put(volume, targetPool); + } + return volumeToPoolObjectMap; + } + + /** + * Executes the managed storage checks for the mapping entered by the user. + */ + protected void executeManagedStorageChecksWhenTargetStoragePoolProvided(StoragePoolVO currentPool, VolumeVO volume, StoragePoolVO targetPool) { + if (!currentPool.isManaged() || currentPool.getPoolType().equals(Storage.StoragePoolType.PowerFlex)) { + return; + } + if (currentPool.getId() == targetPool.getId()) { + return; + } + + Map details = storagePoolDao.getDetails(currentPool.getId()); + if (details != null && Boolean.parseBoolean(details.get(Storage.Capability.ALLOW_MIGRATE_OTHER_POOLS.toString()))) { + return; + } + throw new CloudRuntimeException(String.format("Currently, a volume on managed storage can only be 'migrated' to itself " + "[volumeId=%s, currentStoragePoolId=%s, targetStoragePoolId=%s].", + volume.getUuid(), currentPool.getUuid(), targetPool.getUuid())); + } + + /** + * For each one of the volumes we will map it to a storage pool that is available via the target host. + * An exception is thrown if we cannot find a storage pool that is accessible in the target host to migrate the volume to. + */ + protected void createStoragePoolMappingsForVolumes(VirtualMachineProfile profile, DataCenterDeployment plan, Map volumeToPoolObjectMap, List volumesNotMapped) { + for (Volume volume : volumesNotMapped) { + StoragePoolVO currentPool = storagePoolDao.findById(volume.getPoolId()); + + Host targetHost = null; + if (plan.getHostId() != null) { + targetHost = hostDao.findById(plan.getHostId()); + } + executeManagedStorageChecksWhenTargetStoragePoolNotProvided(targetHost, currentPool, volume); + if (ScopeType.HOST.equals(currentPool.getScope()) || isStorageCrossClusterMigration(plan.getClusterId(), currentPool)) { + createVolumeToStoragePoolMappingIfPossible(profile, plan, volumeToPoolObjectMap, volume, currentPool); + } else if (shouldMapVolume(profile, currentPool)) { + volumeToPoolObjectMap.put(volume, currentPool); + } + } + } + + /** + * Returns true if it should map the volume for a storage pool to migrate. + */ + protected boolean shouldMapVolume(VirtualMachineProfile profile, StoragePoolVO currentPool) { + boolean isManaged = currentPool.isManaged(); + boolean isNotKvm = HypervisorType.KVM != profile.getHypervisorType(); + return isNotKvm || isManaged; + } + + /** + * Executes the managed storage checks for the volumes that the user has not entered a mapping of . + */ + protected void executeManagedStorageChecksWhenTargetStoragePoolNotProvided(Host targetHost, StoragePoolVO currentPool, Volume volume) { + if (!currentPool.isManaged()) { + return; + } + if (targetHost != null && poolHostDao.findByPoolHost(currentPool.getId(), targetHost.getId()) == null) { + throw new CloudRuntimeException(String.format("The target host does not have access to the volume's managed storage pool. [volumeId=%s, storageId=%s, targetHostId=%s].", volume.getUuid(), + currentPool.getUuid(), targetHost.getUuid())); + } + } + + /** + * Return true if the VM migration is a cross cluster migration. + */ + protected boolean isStorageCrossClusterMigration(Long clusterId, StoragePoolVO currentPool) { + return clusterId != null && ScopeType.CLUSTER.equals(currentPool.getScope()) && !currentPool.getClusterId().equals(clusterId); + } + + /** + * We will add a mapping of volume to storage pool if needed. + */ + protected void createVolumeToStoragePoolMappingIfPossible(VirtualMachineProfile profile, DataCenterDeployment plan, Map volumeToPoolObjectMap, Volume volume, + StoragePoolVO currentPool) { + List storagePoolList = getCandidateStoragePoolsToMigrateLocalVolume(profile, plan, volume); + + if (CollectionUtils.isEmpty(storagePoolList)) { + String msg; + if (plan.getHostId() != null) { + Host targetHost = hostDao.findById(plan.getHostId()); + msg = String.format("There are no storage pools available at the target host [%s] to migrate volume [%s]", targetHost.getUuid(), volume.getUuid()); + } else { + Cluster targetCluster = clusterDao.findById(plan.getClusterId()); + msg = String.format("There are no storage pools available in the target cluster [%s] to migrate volume [%s]", targetCluster.getUuid(), volume.getUuid()); + } + throw new CloudRuntimeException(msg); + } + + Collections.shuffle(storagePoolList); + boolean candidatePoolsListContainsVolumeCurrentStoragePool = false; + for (StoragePool storagePool : storagePoolList) { + if (storagePool.getId() == currentPool.getId()) { + candidatePoolsListContainsVolumeCurrentStoragePool = true; + break; + } + } + if (!candidatePoolsListContainsVolumeCurrentStoragePool) { + volumeToPoolObjectMap.put(volume, storagePoolDao.findByUuid(storagePoolList.get(0).getUuid())); + } + } + + /** + * We use {@link StoragePoolAllocator} objects to find storage pools for given DataCenterDeployment where we would be able to allocate the given volume. + */ + protected List getCandidateStoragePoolsToMigrateLocalVolume(VirtualMachineProfile profile, DataCenterDeployment plan, Volume volume) { + List poolList = new ArrayList<>(); + + DiskOfferingVO diskOffering = diskOfferingDao.findById(volume.getDiskOfferingId()); + DiskProfile diskProfile = new DiskProfile(volume, diskOffering, profile.getHypervisorType()); + ExcludeList avoid = new ExcludeList(); + + StoragePoolVO volumeStoragePool = storagePoolDao.findById(volume.getPoolId()); + if (volumeStoragePool.isLocal()) { + diskProfile.setUseLocalStorage(true); + } + for (StoragePoolAllocator allocator : storagePoolAllocators) { + List poolListFromAllocator = allocator.allocateToPool(diskProfile, profile, plan, avoid, StoragePoolAllocator.RETURN_UPTO_ALL); + if (CollectionUtils.isEmpty(poolListFromAllocator)) { + continue; + } + for (StoragePool pool : poolListFromAllocator) { + if (pool.isLocal() || isStorageCrossClusterMigration(plan.getClusterId(), volumeStoragePool)) { + poolList.add(pool); + } + } + } + return poolList; + } + + private static final org.apache.logging.log4j.Logger logger = org.apache.logging.log4j.LogManager.getLogger(VmVolumeMigrationPlanningServiceImpl.class); +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobDispatcher.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobDispatcher.java index d6c55d7038bb..839fb352262c 100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobDispatcher.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobDispatcher.java @@ -18,7 +18,7 @@ import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.context.CallContext; diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobQueueService.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobQueueService.java new file mode 100644 index 000000000000..d9d067cad1f6 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobQueueService.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.net.URI; +import java.util.Map; + +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; + +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.offering.ServiceOffering; + +public interface VmWorkJobQueueService { + VmWorkJobVO createPlaceHolderWork(long instanceId); + + VmWorkJobVO createPlaceHolderWork(long instanceId, String secondaryObjectIdentifier); + + void expungePlaceHolderWork(VmWorkJobVO placeHolder); + + Outcome startVmThroughJobQueue(String vmUuid, Map params, DeploymentPlan planToDeploy, DeploymentPlanner planner); + + Outcome stopVmThroughJobQueue(String vmUuid, boolean cleanup); + + Outcome rebootVmThroughJobQueue(String vmUuid, Map params); + + Outcome migrateVmThroughJobQueue(String vmUuid, long srcHostId, DeployDestination dest); + + Outcome migrateVmAwayThroughJobQueue(String vmUuid, long srcHostId); + + Outcome migrateVmWithStorageThroughJobQueue(String vmUuid, long srcHostId, long destHostId, Map volumeToPool); + + Outcome migrateVmForScaleThroughJobQueue(String vmUuid, long srcHostId, DeployDestination dest, Long newSvcOfferingId); + + Outcome migrateVmStorageThroughJobQueue(String vmUuid, Map volumeToPool); + + Outcome addVmToNetworkThroughJobQueue(VirtualMachine vm, Network network, NicProfile requested); + + Outcome removeNicFromVmThroughJobQueue(VirtualMachine vm, Nic nic); + + Outcome removeVmFromNetworkThroughJobQueue(VirtualMachine vm, Network network, URI broadcastUri); + + Outcome reconfigureVmThroughJobQueue(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, + Map customParameters, boolean reconfiguringOnExistingHost); + + Outcome restoreVirtualMachineThroughJobQueue(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details); + + Outcome updateDefaultNicForVMThroughJobQueue(VirtualMachine vm, Nic nic, Nic defaultNic); + + Outcome updateVmNicThroughJobQueue(VirtualMachine vm, Nic nic, Boolean isNicEnabled); + + VirtualMachine retrieveVmFromJobOutcome(Outcome jobOutcome, String vmUuid, String jobName); + + Object retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(Outcome outcome) throws ResourceUnavailableException, InsufficientCapacityException; +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobQueueServiceImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobQueueServiceImpl.java new file mode 100644 index 000000000000..5702cc94631e --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobQueueServiceImpl.java @@ -0,0 +1,694 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import java.net.URI; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.inject.Inject; +import jakarta.persistence.EntityExistsException; + +import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; +import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; +import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.jobs.AsyncJob; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; +import org.apache.cloudstack.framework.jobs.impl.OutcomeImpl; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.jobs.JobInfo; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.utils.identity.ManagementServerNode; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.offering.ServiceOffering; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeApiService; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.utils.Pair; +import com.cloud.utils.Predicate; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class VmWorkJobQueueServiceImpl implements VmWorkJobQueueService { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected EntityManager entityMgr; + @Inject + protected VMInstanceDao vmDao; + @Inject + protected VmWorkJobDao workJobDao; + @Inject + protected AsyncJobManager jobMgr; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + + public class VmStateSyncOutcome extends OutcomeImpl { + private long vmId; + + public VmStateSyncOutcome(final AsyncJob job, final PowerState desiredPowerState, final long vmId, final Long srcHostIdForMigration) { + super(VirtualMachine.class, job, VirtualMachineManagerImpl.VmJobCheckInterval.value(), new Predicate() { + @Override + public boolean checkCondition() { + final AsyncJobVO jobVo = entityMgr.findById(AsyncJobVO.class, job.getId()); + return jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS; + } + }, VirtualMachineManager.Topics.VM_POWER_STATE, AsyncJob.Topics.JOB_STATE); + this.vmId = vmId; + } + + @Override + protected VirtualMachine retrieve() { + return vmDao.findById(vmId); + } + } + + public class VmJobVirtualMachineOutcome extends OutcomeImpl { + private long vmId; + + public VmJobVirtualMachineOutcome(final AsyncJob job, final long vmId) { + super(VirtualMachine.class, job, VirtualMachineManagerImpl.VmJobCheckInterval.value(), new Predicate() { + @Override + public boolean checkCondition() { + final AsyncJobVO jobVo = entityMgr.findById(AsyncJobVO.class, job.getId()); + return jobVo == null || jobVo.getStatus() != JobInfo.Status.IN_PROGRESS; + } + }, AsyncJob.Topics.JOB_STATE); + this.vmId = vmId; + } + + @Override + protected VirtualMachine retrieve() { + return vmDao.findById(vmId); + } + } + + @Override + public VmWorkJobVO createPlaceHolderWork(final long instanceId) { + return createPlaceHolderWork(instanceId, null); + } + + @Override + public VmWorkJobVO createPlaceHolderWork(final long instanceId, String secondaryObjectIdentifier) { + final VmWorkJobVO workJob = new VmWorkJobVO(""); + + workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_PLACEHOLDER); + workJob.setCmd(""); + workJob.setCmdInfo(""); + + workJob.setAccountId(0); + workJob.setUserId(0); + workJob.setStep(VmWorkJobVO.Step.Starting); + workJob.setVmType(VirtualMachine.Type.Instance); + workJob.setVmInstanceId(instanceId); + if (StringUtils.isNotBlank(secondaryObjectIdentifier)) { + workJob.setSecondaryObjectIdentifier(secondaryObjectIdentifier); + } + workJob.setInitMsid(ManagementServerNode.getManagementServerId()); + + workJobDao.persist(workJob); + + return workJob; + } + + @Override + public void expungePlaceHolderWork(VmWorkJobVO placeHolder) { + if (placeHolder != null) { + workJobDao.expunge(placeHolder.getId()); + } + } + + @Override + public Outcome startVmThroughJobQueue(final String vmUuid, + final Map params, + final DeploymentPlan planToDeploy, final DeploymentPlanner planner) { + String commandName = VmWorkStart.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, VmWorkJobVO.Step.Starting, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkStart workInfo = new VmWorkStart(newVmWorkJobAndInfo.second()); + + workInfo.setPlan(planToDeploy); + workInfo.setParams(params); + if (planner != null) { + workInfo.setDeploymentPlanner(planner.getName()); + } + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmStateSyncOutcome(workJob, + VirtualMachine.PowerState.PowerOn, vmId, null); + } + + @Override + public Outcome stopVmThroughJobQueue(final String vmUuid, final boolean cleanup) { + String commandName = VmWorkStop.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(null, vmUuid, null, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, VmWorkJobVO.Step.Prepare, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkStop workInfo = new VmWorkStop(newVmWorkJobAndInfo.second(), cleanup); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmStateSyncOutcome(workJob, + VirtualMachine.PowerState.PowerOff, vmId, null); + } + + @Override + public Outcome rebootVmThroughJobQueue(final String vmUuid, + final Map params) { + String commandName = VmWorkReboot.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, VmWorkJobVO.Step.Prepare, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkReboot workInfo = new VmWorkReboot(newVmWorkJobAndInfo.second(), params); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, + vmId); + } + + @Override + public Outcome migrateVmThroughJobQueue(final String vmUuid, final long srcHostId, final DeployDestination dest) { + Map volumeStorageMap = dest.getStorageForDisks(); + if (volumeStorageMap != null) { + for (Volume vol : volumeStorageMap.keySet()) { + checkConcurrentJobsPerDatastoreThreshhold(volumeStorageMap.get(vol)); + } + } + + VMInstanceVO vm = vmDao.findByUuid(vmUuid); + Long vmId = vm.getId(); + + String commandName = VmWorkMigrate.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, vmUuid, VirtualMachine.Type.Instance, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkMigrate workInfo = new VmWorkMigrate(newVmWorkJobAndInfo.second(), srcHostId, dest); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmStateSyncOutcome(workJob, + VirtualMachine.PowerState.PowerOn, vmId, vm.getPowerHostId()); + } + + @Override + public Outcome migrateVmAwayThroughJobQueue(final String vmUuid, final long srcHostId) { + VMInstanceVO vm = vmDao.findByUuid(vmUuid); + Long vmId = vm.getId(); + + String commandName = VmWorkMigrateAway.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, vmUuid, VirtualMachine.Type.Instance, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkMigrateAway workInfo = new VmWorkMigrateAway(newVmWorkJobAndInfo.second(), srcHostId); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + + + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmStateSyncOutcome(workJob, VirtualMachine.PowerState.PowerOn, vmId, vm.getPowerHostId()); + } + + @Override + public Outcome migrateVmWithStorageThroughJobQueue( + final String vmUuid, final long srcHostId, final long destHostId, + final Map volumeToPool) { + String commandName = VmWorkMigrateWithStorage.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkMigrateWithStorage workInfo = new VmWorkMigrateWithStorage(newVmWorkJobAndInfo.second(), srcHostId, destHostId, volumeToPool); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmStateSyncOutcome(workJob, + VirtualMachine.PowerState.PowerOn, vmId, destHostId); + } + + @Override + public Outcome migrateVmForScaleThroughJobQueue( + final String vmUuid, final long srcHostId, final DeployDestination dest, final Long newSvcOfferingId) { + String commandName = VmWorkMigrateForScale.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkMigrateForScale workInfo = new VmWorkMigrateForScale(newVmWorkJobAndInfo.second(), srcHostId, dest, newSvcOfferingId); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + void checkConcurrentJobsPerDatastoreThreshhold(final StoragePool destPool) { + final Long threshold = VolumeApiService.ConcurrentMigrationsThresholdPerDatastore.value(); + if (threshold != null && threshold > 0) { + long count = jobMgr.countPendingJobs("\"storageid\":\"" + destPool.getUuid() + "\"", MigrateVMCmd.class.getName(), MigrateVolumeCmd.class.getName(), MigrateVolumeCmdByAdmin.class.getName()); + if (count > threshold) { + throw new CloudRuntimeException("Number of concurrent migration jobs per datastore exceeded the threshold: " + threshold.toString() + ". Please try again after some time."); + } + } + } + + @Override + public Outcome migrateVmStorageThroughJobQueue(final String vmUuid, final Map volumeToPool) { + Collection poolIds = volumeToPool.values(); + Set uniquePoolIds = new HashSet<>(poolIds); + for (Long poolId : uniquePoolIds) { + StoragePoolVO pool = storagePoolDao.findById(poolId); + checkConcurrentJobsPerDatastoreThreshhold(pool); + } + + String commandName = VmWorkStorageMigration.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkStorageMigration workInfo = new VmWorkStorageMigration(newVmWorkJobAndInfo.second(), volumeToPool); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public Outcome addVmToNetworkThroughJobQueue( + final VirtualMachine vm, final Network network, final NicProfile requested) { + Long vmId = vm.getId(); + String commandName = VmWorkAddVmToNetwork.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + final CallContext context = CallContext.current(); + final User user = context.getCallingUser(); + final Account account = context.getCallingAccount(); + + final List pendingWorkJobs = workJobDao.listPendingWorkJobs( + VirtualMachine.Type.Instance, vm.getId(), + VmWorkAddVmToNetwork.class.getName(), network.getUuid()); + + VmWorkJobVO workJob = null; + if (pendingWorkJobs != null && pendingWorkJobs.size() > 0) { + if (pendingWorkJobs.size() > 1) { + throw new CloudRuntimeException(String.format("The number of jobs to add network %s to vm %s are %d", network.getUuid(), vm.getInstanceName(), pendingWorkJobs.size())); + } + workJob = pendingWorkJobs.get(0); + } else { + logger.trace("no jobs to add network {} for vm {} yet", network, vm); + + workJob = createVmWorkJobToAddNetwork(vm, network, requested, context, user, account); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vm.getId()); + } + + VmWorkJobVO createVmWorkJobToAddNetwork( + VirtualMachine vm, + Network network, + NicProfile requested, + CallContext context, + User user, + Account account) { + VmWorkJobVO workJob; + workJob = new VmWorkJobVO(context.getContextId()); + + workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); + workJob.setCmd(VmWorkAddVmToNetwork.class.getName()); + + workJob.setAccountId(account.getId()); + workJob.setUserId(user.getId()); + workJob.setVmType(VirtualMachine.Type.Instance); + workJob.setVmInstanceId(vm.getId()); + workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); + workJob.setSecondaryObjectIdentifier(network.getUuid()); + + // save work context info as there might be some duplicates + final VmWorkAddVmToNetwork workInfo = new VmWorkAddVmToNetwork(user.getId(), account.getId(), vm.getId(), + VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER, network.getId(), requested); + workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); + + try { + jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vm.getId()); + } catch (CloudRuntimeException e) { + if (e.getCause() instanceof EntityExistsException) { + String msg = String.format("A job to add a nic for network %s to vm %s already exists", network.getUuid(), vm.getUuid()); + logger.warn(msg, e); + } + throw e; + } + + return workJob; + } + + @Override + public Outcome removeNicFromVmThroughJobQueue( + final VirtualMachine vm, final Nic nic) { + Long vmId = vm.getId(); + String commandName = VmWorkRemoveNicFromVm.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkRemoveNicFromVm workInfo = new VmWorkRemoveNicFromVm(newVmWorkJobAndInfo.second(), nic.getId()); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public Outcome removeVmFromNetworkThroughJobQueue( + final VirtualMachine vm, final Network network, final URI broadcastUri) { + Long vmId = vm.getId(); + String commandName = VmWorkRemoveVmFromNetwork.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkRemoveVmFromNetwork workInfo = new VmWorkRemoveVmFromNetwork(newVmWorkJobAndInfo.second(), network, broadcastUri); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public Outcome reconfigureVmThroughJobQueue( + final String vmUuid, final ServiceOffering oldServiceOffering, final ServiceOffering newServiceOffering, Map customParameters, final boolean reconfiguringOnExistingHost) { + String commandName = VmWorkReconfigure.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmUuid, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + Long vmId = pendingWorkJob.second(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkReconfigure workInfo = new VmWorkReconfigure(newVmWorkJobAndInfo.second(), oldServiceOffering.getId(), newServiceOffering.getId(), customParameters, reconfiguringOnExistingHost); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public Outcome restoreVirtualMachineThroughJobQueue(final long vmId, final Long newTemplateId, final Long rootDiskOfferingId, final boolean expunge, Map details) { + String commandName = VmWorkRestore.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkRestore workInfo = new VmWorkRestore(newVmWorkJobAndInfo.second(), newTemplateId, rootDiskOfferingId, expunge, details); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public Outcome updateDefaultNicForVMThroughJobQueue(final VirtualMachine vm, final Nic nic, final Nic defaultNic) { + Long vmId = vm.getId(); + String commandName = VmWorkUpdateDefaultNic.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkUpdateDefaultNic workInfo = new VmWorkUpdateDefaultNic(newVmWorkJobAndInfo.second(), nic.getId(), defaultNic.getId()); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public Outcome updateVmNicThroughJobQueue(final VirtualMachine vm, final Nic nic, final Boolean isNicEnabled) { + Long vmId = vm.getId(); + String commandName = VmWorkUpdateNic.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkUpdateNic workInfo = new VmWorkUpdateNic(newVmWorkJobAndInfo.second(), nic.getId(), isNicEnabled); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @Override + public VirtualMachine retrieveVmFromJobOutcome(Outcome jobOutcome, String vmUuid, String jobName) { + try { + return jobOutcome.get(); + } catch (InterruptedException | java.util.concurrent.ExecutionException e) { + throw new RuntimeException(String.format("Unable to retrieve result from job \"%s\" due to [%s]. VM {\"uuid\": \"%s\"}.", jobName, e.getMessage(), vmUuid), e); + } + } + + @Override + public Object retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(Outcome outcome) throws ResourceUnavailableException, InsufficientCapacityException { + Object jobResult = jobMgr.unmarshallResultObject(outcome.getJob()); + + if (jobResult == null) { + return null; + } + + if (jobResult instanceof AgentUnavailableException) { + throw (AgentUnavailableException) jobResult; + } + + if (jobResult instanceof InsufficientServerCapacityException) { + throw (InsufficientServerCapacityException) jobResult; + } + + if (jobResult instanceof ResourceUnavailableException) { + throw (ResourceUnavailableException) jobResult; + } + + if (jobResult instanceof InsufficientCapacityException) { + throw (InsufficientCapacityException) jobResult; + } + + if (jobResult instanceof ConcurrentOperationException) { + throw (ConcurrentOperationException) jobResult; + } + + if (jobResult instanceof RuntimeException) { + throw (RuntimeException) jobResult; + } + + if (jobResult instanceof Throwable) { + throw new RuntimeException("Unexpected exception", (Throwable)jobResult); + } + + return jobResult; + } + + Pair retrievePendingWorkJob(String vmUuid, String commandName) { + return retrievePendingWorkJob(null, vmUuid, VirtualMachine.Type.Instance, commandName); + } + + Pair retrievePendingWorkJob(Long id, String commandName) { + return retrievePendingWorkJob(id, null, VirtualMachine.Type.Instance, commandName); + } + + Pair retrievePendingWorkJob(Long vmId, String vmUuid, VirtualMachine.Type vmType, String commandName) { + if (vmId == null) { + VMInstanceVO vm = vmDao.findByUuid(vmUuid); + + if (vm == null) { + String message = String.format("Could not find a VM with the uuid [%s]. Unable to continue validations with command [%s] through job queue.", vmUuid, commandName); + logger.error(message); + throw new RuntimeException(message); + } + + vmId = vm.getId(); + + if (vmType == null) { + vmType = vm.getType(); + } + } + + List pendingWorkJobs = workJobDao.listPendingWorkJobs(vmType, vmId, commandName); + + if (CollectionUtils.isNotEmpty(pendingWorkJobs)) { + return new Pair<>(pendingWorkJobs.get(0), vmId); + } + + return new Pair<>(null, vmId); + } + + Pair createWorkJobAndWorkInfo(String commandName, Long vmId) { + return createWorkJobAndWorkInfo(commandName, null, vmId); + } + + Pair createWorkJobAndWorkInfo(String commandName, VmWorkJobVO.Step step, Long vmId) { + CallContext context = CallContext.current(); + long userId = context.getCallingUser().getId(); + long accountId = context.getCallingAccount().getId(); + + VmWorkJobVO workJob = new VmWorkJobVO(context.getContextId()); + workJob.setDispatcher(VmWorkConstants.VM_WORK_JOB_DISPATCHER); + workJob.setCmd(commandName); + workJob.setAccountId(accountId); + workJob.setUserId(userId); + + if (step != null) { + workJob.setStep(step); + } + + workJob.setVmType(VirtualMachine.Type.Instance); + workJob.setVmInstanceId(vmId); + workJob.setRelated(AsyncJobExecutionContext.getOriginJobId()); + + VmWork workInfo = new VmWork(userId, accountId, vmId, VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER); + + return new Pair<>(workJob, workInfo); + } + + void setCmdInfoAndSubmitAsyncJob(VmWorkJobVO workJob, VmWork workInfo, Long vmId) { + workJob.setCmdInfo(VmWorkSerializer.serialize(workInfo)); + jobMgr.submitAsyncJob(workJob, VmWorkConstants.VM_WORK_QUEUE, vmId); + } +} diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobWakeupDispatcher.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobWakeupDispatcher.java index 1b050ffd9de6..d481136f459d 100644 --- a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobWakeupDispatcher.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkJobWakeupDispatcher.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.context.CallContext; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java index a94cbb2bf181..640470e99d4e 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VMEntityManagerImpl.java @@ -20,7 +20,7 @@ import java.util.Map; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.engine.cloud.entity.api.db.VMEntityVO; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntityImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntityImpl.java index aa39db155053..1820eb00d8c9 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntityImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/VirtualMachineEntityImpl.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.HashMap; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.commons.collections.MapUtils; import org.springframework.stereotype.Component; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceManagerImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceManagerImpl.java index e48481324df6..6695521dd2f9 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceManagerImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/DataCenterResourceManagerImpl.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.engine.datacenter.entity.api; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntityImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntityImpl.java index ee434bb2291c..f6a6ff8d718f 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntityImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/ZoneEntityImpl.java @@ -24,8 +24,8 @@ import java.util.List; import java.util.Map; -import javax.ws.rs.GET; -import javax.ws.rs.Path; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; import org.apache.cloudstack.engine.datacenter.entity.api.DataCenterResourceEntity.State.Event; import org.apache.cloudstack.engine.datacenter.entity.api.db.EngineDataCenterVO; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/ClusterDetailsVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/ClusterDetailsVO.java index a59a45abb487..bbe8d81a311e 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/ClusterDetailsVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/ClusterDetailsVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.engine.datacenter.entity.api.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "cluster_details") diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/DcDetailVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/DcDetailVO.java index ddbb812764da..cce5d11e46bc 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/DcDetailVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/DcDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.engine.datacenter.entity.api.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "data_center_details") diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java index 39ab83fab600..f67536fbcfc2 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java @@ -31,17 +31,17 @@ import org.apache.cloudstack.util.HypervisorTypeConverter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineDataCenterVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineDataCenterVO.java index 5f1203c024a4..b5cdd10ba765 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineDataCenterVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineDataCenterVO.java @@ -20,18 +20,18 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.engine.datacenter.entity.api.DataCenterResourceEntity.State; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java index cd3f6b857a29..a35ecff61faf 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.engine.datacenter.entity.api.DataCenterResourceEntity.State; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java index eec2b011b3e8..fe3b625c34d4 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java @@ -21,22 +21,22 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.cpu.CPU; import org.apache.cloudstack.api.Identity; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineClusterDaoImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineClusterDaoImpl.java index fa8b782f662e..83a6216aaf99 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineClusterDaoImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineClusterDaoImpl.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineDataCenterDaoImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineDataCenterDaoImpl.java index 96dfdc00d676..b2d41dfbb799 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineDataCenterDaoImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineDataCenterDaoImpl.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.Random; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.springframework.stereotype.Component; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java index 7f6571becc83..6af839b928b8 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java @@ -22,8 +22,8 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; -import javax.persistence.TableGenerator; +import jakarta.inject.Inject; +import jakarta.persistence.TableGenerator; import org.springframework.stereotype.Component; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index 8639f006383f..a906ac6025a0 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -28,7 +28,7 @@ import java.util.Map; import java.util.Map.Entry; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.cloud.entity.api.NetworkEntity; import org.apache.cloudstack.engine.cloud.entity.api.TemplateEntity; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java index 5a8dc3038aa8..ecfcaddb7bb4 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationService.java new file mode 100644 index 000000000000..7807aad8e428 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationService.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import org.apache.cloudstack.acl.ControlledEntity.ACLType; + +import com.cloud.dc.DataCenterVO; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.network.Network; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.user.Account; +import com.cloud.utils.Pair; + +public interface GuestNetworkCreationPreparationService { + + GuestNetworkCreationPreparation prepareGuestNetworkCreation(long networkOfferingId, String gateway, String cidr, String vlanId, + boolean bypassVlanOverlapCheck, String networkDomain, Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, + Boolean subdomainAccess, String ip6Gateway, String ip6Cidr, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, + Boolean isPrivateNetwork, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, + Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic); +} + +class GuestNetworkCreationPreparation { + private final NetworkOfferingVO networkOffering; + private final DataCenterVO zone; + private final String networkDomain; + private final Boolean subdomainAccess; + private final DataCenterDeployment plan; + private final NetworkVO predefinedNetwork; + + GuestNetworkCreationPreparation(NetworkOfferingVO networkOffering, DataCenterVO zone, String networkDomain, Boolean subdomainAccess, + DataCenterDeployment plan, NetworkVO predefinedNetwork) { + this.networkOffering = networkOffering; + this.zone = zone; + this.networkDomain = networkDomain; + this.subdomainAccess = subdomainAccess; + this.plan = plan; + this.predefinedNetwork = predefinedNetwork; + } + + public NetworkOfferingVO getNetworkOffering() { + return networkOffering; + } + + public DataCenterVO getZone() { + return zone; + } + + public String getNetworkDomain() { + return networkDomain; + } + + public Boolean getSubdomainAccess() { + return subdomainAccess; + } + + public DataCenterDeployment getPlan() { + return plan; + } + + public NetworkVO getPredefinedNetwork() { + return predefinedNetwork; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationServiceImpl.java new file mode 100644 index 000000000000..8a663c3ffec9 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationServiceImpl.java @@ -0,0 +1,370 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.domain.Domain; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.utils.UuidUtils; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.net.NetUtils; + +@Component +public class GuestNetworkCreationPreparationServiceImpl implements GuestNetworkCreationPreparationService { + + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected DataCenterDao dcDao; + @Inject + protected NetworkDao networksDao; + @Inject + protected NetworkModel networkModel; + @Inject + protected EntityManager entityMgr; + @Inject + protected NetworkOfferingVlanValidationService networkOfferingVlanValidationService; + + @Override + public GuestNetworkCreationPreparation prepareGuestNetworkCreation(final long networkOfferingId, final String gateway, final String cidr, String vlanId, + boolean bypassVlanOverlapCheck, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork physicalNetwork, + final long zoneId, final ACLType aclType, Boolean subdomainAccess, final String ip6Gateway, final String ip6Cidr, final String isolatedPvlan, + Network.PVlanType isolatedPvlanType, String externalId, final Boolean isPrivateNetwork, String routerIp, String routerIpv6, final String ip4Dns1, + final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, + boolean keepMacAddressOnPublicNic) { + final NetworkOfferingVO networkOffering = networkOfferingDao.findById(networkOfferingId); + final DataCenterVO zone = dcDao.findById(zoneId); + + if (networkOffering.getTrafficType() != TrafficType.Guest) { + return null; + } + + validateNetworkOffering(networkOffering); + validatePhysicalNetwork(physicalNetwork); + + boolean ipv6 = false; + if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) { + ipv6 = true; + } + + if (zone.getNetworkType() == NetworkType.Basic) { + subdomainAccess = validateAndNormalizeBasicZone(networkOffering, zone, vlanId, domainId, aclType, subdomainAccess); + if (vlanId == null) { + vlanId = Vlan.UNTAGGED; + } + } else if (zone.getNetworkType() == NetworkType.Advanced) { + validateAdvancedZone(networkOffering, zone, isolatedPvlan); + } + + if (ipv6 && !GuestType.Shared.equals(networkOffering.getGuestType())) { + networkModel.checkIp6CidrSizeEqualTo64(ip6Cidr); + } + + networkOfferingVlanValidationService.validateGuestNetworkOfferingVlan(vlanId, isolatedPvlan, bypassVlanOverlapCheck, networkOffering, physicalNetwork, zone, zoneId, + owner, isPrivateNetwork); + + networkDomain = normalizeNetworkDomain(networkOfferingId, networkDomain, owner, domainId, zoneId, aclType, zone); + + validateCidrRequirements(networkOffering, zone, cidr, ip6Cidr); + networkOfferingVlanValidationService.checkL2OfferingServices(networkOffering); + validateBasicZoneCidr(zone, cidr); + validateGuestCidr(networkOffering, cidr); + + Long physicalNetworkId = null; + if (physicalNetwork != null) { + physicalNetworkId = physicalNetwork.getId(); + } + final DataCenterDeployment plan = new DataCenterDeployment(zoneId, null, null, null, null, physicalNetworkId); + final NetworkVO userNetwork = buildPredefinedNetwork(networkDomain, gateway, cidr, vlanId, ip6Gateway, ip6Cidr, isolatedPvlan, isolatedPvlanType, externalId, + routerIp, routerIpv6, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic, physicalNetwork, physicalNetworkId, zone); + + return new GuestNetworkCreationPreparation(networkOffering, zone, networkDomain, subdomainAccess, plan, userNetwork); + } + + protected void validateNetworkOffering(final NetworkOfferingVO networkOffering) { + if (networkOffering.getState() != NetworkOffering.State.Enabled) { + final InvalidParameterValueException ex = new InvalidParameterValueException( + "Can't use specified network offering id as its state is not " + NetworkOffering.State.Enabled); + ex.addProxyObject(networkOffering.getUuid(), "networkOfferingId"); + throw ex; + } + } + + protected void validatePhysicalNetwork(final PhysicalNetwork physicalNetwork) { + if (physicalNetwork.getState() != PhysicalNetwork.State.Enabled) { + final InvalidParameterValueException ex = new InvalidParameterValueException("Specified physical network id is" + " in incorrect state:" + physicalNetwork.getState()); + ex.addProxyObject(physicalNetwork.getUuid(), "physicalNetworkId"); + throw ex; + } + } + + protected Boolean validateAndNormalizeBasicZone(final NetworkOfferingVO networkOffering, final DataCenterVO zone, final String vlanId, final Long domainId, + final ACLType aclType, Boolean subdomainAccess) { + if (aclType == null || aclType != ACLType.Domain) { + throw new InvalidParameterValueException("Only AclType=Domain can be specified for network creation in Basic zone"); + } + + final List guestNetworks = networksDao.listByZoneAndTrafficType(zone.getId(), TrafficType.Guest); + if (!guestNetworks.isEmpty()) { + throw new InvalidParameterValueException("Can't have more than one Guest network in zone with network type " + NetworkType.Basic); + } + + if (!(networkOffering.getGuestType() == GuestType.Shared && !networkModel.areServicesSupportedByNetworkOffering(networkOffering.getId(), Service.SourceNat))) { + throw new InvalidParameterValueException("For zone of type " + NetworkType.Basic + " only offerings of " + "guestType " + GuestType.Shared + " with disabled " + + Service.SourceNat.getName() + " service are allowed"); + } + + if (domainId == null || domainId != Domain.ROOT_DOMAIN) { + throw new InvalidParameterValueException("Guest network in Basic zone should be dedicated to ROOT domain"); + } + + if (subdomainAccess == null) { + subdomainAccess = true; + } else if (!subdomainAccess) { + throw new InvalidParameterValueException("Subdomain access should be set to true for the" + " guest network in the Basic zone"); + } + + if (vlanId != null && !vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) { + throw new InvalidParameterValueException("Only vlan " + Vlan.UNTAGGED + " can be created in " + "the zone of type " + NetworkType.Basic); + } + return subdomainAccess; + } + + protected void validateAdvancedZone(final NetworkOfferingVO networkOffering, final DataCenterVO zone, final String isolatedPvlan) { + if (zone.isSecurityGroupEnabled()) { + if (isolatedPvlan != null) { + throw new InvalidParameterValueException("Isolated Private VLAN is not supported with security group!"); + } + if ((networkOffering.getGuestType() != GuestType.Shared) && (networkOffering.getGuestType() != GuestType.L2)) { + throw new InvalidParameterValueException("Only shared or L2 guest network can be created in security group enabled zone"); + } + if (networkModel.areServicesSupportedByNetworkOffering(networkOffering.getId(), Service.SourceNat)) { + throw new InvalidParameterValueException("Service SourceNat is not allowed in security group enabled zone"); + } + } + + if (networkOffering.isElasticIp() || networkOffering.isElasticLb()) { + throw new InvalidParameterValueException("Elastic IP and Elastic LB services are supported in zone of type " + NetworkType.Basic); + } + } + + protected String normalizeNetworkDomain(final long networkOfferingId, String networkDomain, final Account owner, final Long domainId, final long zoneId, + final ACLType aclType, final DataCenterVO zone) { + if (networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.Dns)) { + final Map dnsCapabilities = networkModel.getNetworkOfferingServiceCapabilities(entityMgr.findById(NetworkOffering.class, networkOfferingId), + Service.Dns); + final String isUpdateDnsSupported = dnsCapabilities.get(Capability.AllowDnsSuffixModification); + if (isUpdateDnsSupported == null || !Boolean.valueOf(isUpdateDnsSupported)) { + if (networkDomain != null) { + throw new InvalidParameterValueException(String.format("Domain name change is not supported by network offering id=%d in zone %s", networkOfferingId, zone)); + } + } else { + if (networkDomain == null) { + if (aclType == ACLType.Domain) { + networkDomain = networkModel.getDomainNetworkDomain(domainId, zoneId); + } else if (aclType == ACLType.Account) { + networkDomain = networkModel.getAccountNetworkDomain(owner.getId(), zoneId); + } + + if (networkDomain == null) { + networkDomain = "cs" + Long.toHexString(owner.getId()) + NetworkOrchestrationService.GuestDomainSuffix.valueIn(zoneId); + } + } else { + if (!NetUtils.verifyDomainName(networkDomain)) { + throw new InvalidParameterValueException("Invalid network domain. Total length shouldn't exceed 190 chars. Each domain " + + "label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " + + "and the hyphen ('-'); can't start or end with \"-\""); + } + } + } + } + return networkDomain; + } + + protected void validateCidrRequirements(final NetworkOfferingVO networkOffering, final DataCenterVO zone, final String cidr, final String ip6Cidr) { + final boolean cidrRequired = zone.getNetworkType() == NetworkType.Advanced + && networkOffering.getTrafficType() == TrafficType.Guest + && (networkOffering.getGuestType() == GuestType.Shared || (networkOffering.getGuestType() == GuestType.Isolated + && !networkModel.areServicesSupportedByNetworkOffering(networkOffering.getId(), Service.SourceNat) + && !networkModel.areServicesSupportedByNetworkOffering(networkOffering.getId(), Service.Gateway))); + if (cidr == null && ip6Cidr == null && cidrRequired) { + if (networkOffering.getGuestType() == GuestType.Shared) { + throw new InvalidParameterValueException(String.format("Gateway/netmask are required when creating %s networks.", Network.GuestType.Shared)); + } else { + throw new InvalidParameterValueException("gateway/netmask are required when create network of" + " type " + GuestType.Isolated + " with service " + + Service.SourceNat.getName() + " disabled"); + } + } + } + + protected void validateBasicZoneCidr(final DataCenterVO zone, final String cidr) { + if (zone.getNetworkType() == NetworkType.Basic && cidr != null) { + throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask can't be specified for zone of type " + NetworkType.Basic); + } + } + + protected void validateGuestCidr(final NetworkOfferingVO networkOffering, final String cidr) { + if (cidr != null && (networkOffering.getGuestType() == Network.GuestType.Isolated && networkOffering.getTrafficType() == TrafficType.Guest) && + !NetUtils.validateGuestCidr(cidr, !ConfigurationManager.AllowNonRFC1918CompliantIPs.value())) { + throw new InvalidParameterValueException("Virtual Guest Cidr " + cidr + " is not RFC 1918 or 6598 compliant"); + } + } + + protected NetworkVO buildPredefinedNetwork(final String networkDomain, final String gateway, final String cidr, final String vlanId, final String ip6Gateway, + final String ip6Cidr, final String isolatedPvlan, final Network.PVlanType isolatedPvlanType, final String externalId, final String routerIp, + final String routerIpv6, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Pair vrIfaceMTUs, + final Integer networkCidrSize, final boolean keepMacAddressOnPublicNic, final PhysicalNetwork physicalNetwork, final Long physicalNetworkId, final DataCenterVO zone) { + final NetworkVO userNetwork = new NetworkVO(); + userNetwork.setNetworkDomain(networkDomain); + + if (cidr != null && gateway != null) { + userNetwork.setCidr(cidr); + userNetwork.setGateway(gateway); + } + + if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) { + userNetwork.setIp6Cidr(ip6Cidr); + userNetwork.setIp6Gateway(ip6Gateway); + } + + if (externalId != null) { + userNetwork.setExternalId(externalId); + } + + if (StringUtils.isNotBlank(routerIp)) { + userNetwork.setRouterIp(routerIp); + } + + if (StringUtils.isNotBlank(routerIpv6)) { + userNetwork.setRouterIpv6(routerIpv6); + } + + setMtu(vrIfaceMTUs, userNetwork); + setDns(ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, userNetwork); + setBroadcast(vlanId, isolatedPvlan, isolatedPvlanType, physicalNetwork, physicalNetworkId, zone, userNetwork); + + userNetwork.setNetworkCidrSize(networkCidrSize); + userNetwork.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); + return userNetwork; + } + + protected void setMtu(final Pair vrIfaceMTUs, final NetworkVO userNetwork) { + if (vrIfaceMTUs != null) { + if (vrIfaceMTUs.first() != null && vrIfaceMTUs.first() > 0) { + userNetwork.setPublicMtu(vrIfaceMTUs.first()); + } else { + userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue())); + } + + if (vrIfaceMTUs.second() != null && vrIfaceMTUs.second() > 0) { + userNetwork.setPrivateMtu(vrIfaceMTUs.second()); + } else { + userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue())); + } + } else { + userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue())); + userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue())); + } + } + + protected void setDns(final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final NetworkVO userNetwork) { + if (!GuestType.L2.equals(userNetwork.getGuestType())) { + if (StringUtils.isNotBlank(ip4Dns1)) { + userNetwork.setDns1(ip4Dns1); + } + if (StringUtils.isNotBlank(ip4Dns2)) { + userNetwork.setDns2(ip4Dns2); + } + if (StringUtils.isNotBlank(ip6Dns1)) { + userNetwork.setIp6Dns1(ip6Dns1); + } + if (StringUtils.isNotBlank(ip6Dns2)) { + userNetwork.setIp6Dns2(ip6Dns2); + } + } + } + + protected void setBroadcast(final String vlanId, final String isolatedPvlan, final Network.PVlanType isolatedPvlanType, final PhysicalNetwork physicalNetwork, + final Long physicalNetworkId, final DataCenterVO zone, final NetworkVO userNetwork) { + if (vlanId != null) { + if (isolatedPvlan == null) { + URI uri = null; + if (UuidUtils.isUuid(vlanId)) { + userNetwork.setVlanIdAsUUID(vlanId); + } else { + uri = networkOfferingVlanValidationService.encodeVlanIdIntoBroadcastUri(vlanId, physicalNetwork); + } + + if (networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString()).size() > 0) { + throw new InvalidParameterValueException(String.format("Network with vlan %s already exists or overlaps with other network pvlans in zone %s", vlanId, zone)); + } + + userNetwork.setBroadcastUri(uri); + if (!vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) { + userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan); + } else { + userNetwork.setBroadcastDomainType(BroadcastDomainType.Native); + } + } else { + if (vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) { + throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!"); + } + URI uri = NetUtils.generateUriForPvlan(vlanId, isolatedPvlan, isolatedPvlanType.toString()); + if (networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) { + throw new InvalidParameterValueException(String.format( + "Network with primary vlan %s and secondary vlan %s type %s already exists or overlaps with other network pvlans in zone %s", + vlanId, isolatedPvlan, isolatedPvlanType, zone)); + } + userNetwork.setBroadcastUri(uri); + userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan); + userNetwork.setPvlanType(isolatedPvlanType); + } + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupService.java new file mode 100644 index 000000000000..1bf8ea32b84e --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupService.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.agent.api.StartupCommand; +import com.cloud.exception.ConnectionException; +import com.cloud.host.Host; + +public interface NetworkHostSetupService { + + void processConnect(Host host, StartupCommand cmd, boolean forRebalance) throws ConnectionException; +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupServiceImpl.java new file mode 100644 index 000000000000..44308bf18243 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupServiceImpl.java @@ -0,0 +1,143 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.CheckNetworkAnswer; +import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.alert.AlertManager; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.ConnectionException; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetworkSetupInfo; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao; +import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO; +import com.cloud.network.dao.PhysicalNetworkVO; + +@Component +public class NetworkHostSetupServiceImpl implements NetworkHostSetupService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected PhysicalNetworkDao physicalNetworkDao; + @Inject + protected PhysicalNetworkTrafficTypeDao physicalNetworkTrafficTypeDao; + @Inject + protected AgentManager agentManager; + @Inject + protected AlertManager alertManager; + + @Override + public void processConnect(final Host host, final StartupCommand cmd, final boolean forRebalance) throws ConnectionException { + if (!(cmd instanceof StartupRoutingCommand) || cmd.isConnectionTransferred()) { + return; + } + final StartupRoutingCommand startup = (StartupRoutingCommand) cmd; + final DataCenterVO dc = resolveDataCenter(startup); + final long dcId = dc.getId(); + final HypervisorType hypervisorType = startup.getHypervisorType(); + + logger.debug("Host's hypervisorType is: {}", hypervisorType); + + final List networkInfoList = buildNetworkSetupInfo(dcId, hypervisorType); + checkNetworkSetup(host, networkInfoList, dcId); + } + + private DataCenterVO resolveDataCenter(final StartupRoutingCommand startup) { + final String dataCenter = startup.getDataCenter(); + DataCenterVO dc = dataCenterDao.findByName(dataCenter); + if (dc == null) { + try { + final long dcId = Long.parseLong(dataCenter); + dc = dataCenterDao.findById(dcId); + } catch (final NumberFormatException e) { + } + } + if (dc == null) { + throw new IllegalArgumentException("Host " + startup.getPrivateIpAddress() + " sent incorrect data center: " + dataCenter); + } + return dc; + } + + private List buildNetworkSetupInfo(final long dcId, final HypervisorType hypervisorType) { + final List networkInfoList = new ArrayList<>(); + final List physicalNtwkList = physicalNetworkDao.listByZone(dcId); + for (final PhysicalNetworkVO pNtwk : physicalNtwkList) { + final String publicName = physicalNetworkTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Public, hypervisorType); + final String privateName = physicalNetworkTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Management, hypervisorType); + final String guestName = physicalNetworkTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Guest, hypervisorType); + final String storageName = physicalNetworkTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Storage, hypervisorType); + // String controlName = physicalNetworkTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Control, hypervisorType); + final PhysicalNetworkSetupInfo info = new PhysicalNetworkSetupInfo(); + info.setPhysicalNetworkId(pNtwk.getId()); + info.setGuestNetworkName(guestName); + info.setPrivateNetworkName(privateName); + info.setPublicNetworkName(publicName); + info.setStorageNetworkName(storageName); + final PhysicalNetworkTrafficTypeVO mgmtTraffic = physicalNetworkTrafficTypeDao.findBy(pNtwk.getId(), TrafficType.Management); + if (mgmtTraffic != null) { + final String vlan = mgmtTraffic.getVlan(); + info.setMgmtVlan(vlan); + } + networkInfoList.add(info); + } + return networkInfoList; + } + + private void checkNetworkSetup(final Host host, final List networkInfoList, final long dcId) throws ConnectionException { + logger.debug("Sending CheckNetworkCommand to check the Network is setup correctly on Agent"); + final CheckNetworkCommand nwCmd = new CheckNetworkCommand(networkInfoList); + + final CheckNetworkAnswer answer = (CheckNetworkAnswer) agentManager.easySend(host.getId(), nwCmd); + + if (answer == null) { + logger.warn("Unable to get an answer to the CheckNetworkCommand from agent: {}", host); + throw new ConnectionException(true, String.format("Unable to get an answer to the CheckNetworkCommand from agent: %s", host)); + } + + if (!answer.getResult()) { + logger.warn("Unable to setup agent {} due to {}", host, answer.getDetails()); + final String msg = "Incorrect Network setup on agent, Reinitialize agent after network names are setup, details : " + answer.getDetails(); + alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, dcId, host.getPodId(), msg, msg); + throw new ConnectionException(true, msg); + } else { + if (answer.needReconnect()) { + throw new ConnectionException(false, "Reinitialize agent after network setup."); + } + logger.debug("Network setup is correct on Agent"); + return; + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationService.java new file mode 100644 index 000000000000..270d5f975bf1 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationService.java @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.net.URI; + +import com.cloud.dc.DataCenterVO; +import com.cloud.network.PhysicalNetwork; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.user.Account; + +public interface NetworkOfferingVlanValidationService { + + boolean isSharedNetworkWithoutSpecifyVlan(NetworkOffering offering); + + boolean isPrivateGatewayWithoutSpecifyVlan(NetworkOffering offering); + + URI encodeVlanIdIntoBroadcastUri(String vlanId, PhysicalNetwork physicalNetwork); + + void validateGuestNetworkOfferingVlan(String vlanId, String isolatedPvlan, boolean bypassVlanOverlapCheck, + NetworkOfferingVO offering, PhysicalNetwork physicalNetwork, DataCenterVO zone, long zoneId, Account owner, boolean isPrivateNetwork); + + void checkL2OfferingServices(NetworkOfferingVO offering); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationServiceImpl.java new file mode 100644 index 000000000000..144f75a7a4bd --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationServiceImpl.java @@ -0,0 +1,200 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.net.URI; +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DataCenterVnetVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DataCenterVnetDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.dao.AccountGuestVlanMapDao; +import com.cloud.network.dao.AccountGuestVlanMapVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.UuidUtils; + +@Component +public class NetworkOfferingVlanValidationServiceImpl implements NetworkOfferingVlanValidationService { + + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected NetworkDao networksDao; + @Inject + protected DataCenterVnetDao dataCenterVnetDao; + @Inject + protected AccountGuestVlanMapDao accountGuestVlanMapDao; + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected NetworkModel networkModel; + + @Override + public boolean isSharedNetworkWithoutSpecifyVlan(NetworkOffering offering) { + if (offering == null || offering.getTrafficType() != TrafficType.Guest || offering.getGuestType() != GuestType.Shared) { + return false; + } + return !offering.isSpecifyVlan(); + } + + @Override + public boolean isPrivateGatewayWithoutSpecifyVlan(NetworkOffering offering) { + return offering.getId() == networkOfferingDao.findByUniqueName(NetworkOffering.SystemPrivateGatewayNetworkOfferingWithoutVlan).getId(); + } + + @Override + public URI encodeVlanIdIntoBroadcastUri(String vlanId, PhysicalNetwork physicalNetwork) { + if (physicalNetwork == null) { + throw new InvalidParameterValueException(String.format("Failed to encode VLAN/VXLAN %s into a Broadcast URI. Physical Network cannot be null.", vlanId)); + } + + if (!physicalNetwork.getIsolationMethods().isEmpty() && StringUtils.isNotBlank(physicalNetwork.getIsolationMethods().get(0))) { + String isolationMethod = physicalNetwork.getIsolationMethods().get(0).toLowerCase(); + String vxlan = BroadcastDomainType.Vxlan.toString().toLowerCase(); + if (isolationMethod.equals(vxlan)) { + if (StringUtils.isNotBlank(vlanId) && UuidUtils.isUuid(vlanId)) { + return BroadcastDomainType.Vxlan.toUri(vlanId); + } + return BroadcastDomainType.encodeStringIntoBroadcastUri(vlanId, BroadcastDomainType.Vxlan); + } + } + if (StringUtils.isNotBlank(vlanId) && UuidUtils.isUuid(vlanId)) { + return BroadcastDomainType.Vlan.toUri(vlanId); + } + return BroadcastDomainType.fromString(vlanId); + } + + @Override + public void validateGuestNetworkOfferingVlan(String vlanId, String isolatedPvlan, boolean bypassVlanOverlapCheck, + NetworkOfferingVO offering, PhysicalNetwork physicalNetwork, DataCenterVO zone, long zoneId, Account owner, boolean isPrivateNetwork) { + final boolean vlanSpecified = vlanId != null; + if (vlanSpecified != offering.isSpecifyVlan()) { + if (vlanSpecified) { + if (!isSharedNetworkWithoutSpecifyVlan(offering) && !isPrivateGatewayWithoutSpecifyVlan(offering)) { + throw new InvalidParameterValueException("Can't specify vlan; corresponding offering says specifyVlan=false"); + } + } else { + throw new InvalidParameterValueException("Vlan has to be specified; corresponding offering says specifyVlan=true"); + } + } + + if (!vlanSpecified) { + return; + } + + URI uri = encodeVlanIdIntoBroadcastUri(vlanId, physicalNetwork); + URI secondaryUri = StringUtils.isNotBlank(isolatedPvlan) ? BroadcastDomainType.fromString(isolatedPvlan) : null; + if (isSharedNetworkWithoutSpecifyVlan(offering) || isPrivateGatewayWithoutSpecifyVlan(offering)) { + bypassVlanOverlapCheck = true; + } + if (!(bypassVlanOverlapCheck && (offering.getGuestType() == GuestType.Shared || isPrivateNetwork)) + && dataCenterDao.findVnet(zoneId, physicalNetwork.getId(), BroadcastDomainType.getValue(uri)).size() > 0) { + throw new InvalidParameterValueException("The VLAN tag to use for new guest network, " + vlanId + + " is already being used for dynamic vlan allocation for the guest network in zone " + zone.getName()); + } + if (secondaryUri != null && !(bypassVlanOverlapCheck && offering.getGuestType() == GuestType.Shared) && + dataCenterDao.findVnet(zoneId, physicalNetwork.getId(), BroadcastDomainType.getValue(secondaryUri)).size() > 0) { + throw new InvalidParameterValueException(String.format( + "The VLAN tag for isolated PVLAN %s is already being used for dynamic vlan allocation for the guest network in zone %s", + isolatedPvlan, zone)); + } + if (!UuidUtils.isUuid(vlanId)) { + validateVlanOverlapAndDedicatedRanges(vlanId, isolatedPvlan, bypassVlanOverlapCheck, offering, zone, zoneId, owner, isPrivateNetwork, uri, secondaryUri); + } + } + + protected void validateVlanOverlapAndDedicatedRanges(String vlanId, String isolatedPvlan, boolean bypassVlanOverlapCheck, + NetworkOfferingVO offering, DataCenterVO zone, long zoneId, Account owner, boolean isPrivateNetwork, URI uri, URI secondaryUri) { + if (!hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, offering, isPrivateNetwork)) { + if (networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) { + throw new InvalidParameterValueException(String.format( + "Network with vlan %s already exists or overlaps with other network vlans in zone %s", + vlanId, zone)); + } else if (secondaryUri != null && networksDao.listByZoneAndUriAndGuestType(zoneId, secondaryUri.toString(), null).size() > 0) { + throw new InvalidParameterValueException(String.format( + "Network with vlan %s already exists or overlaps with other network vlans in zone %s", + isolatedPvlan, zone)); + } else { + validateDedicatedGuestVlanOwnership(vlanId, zoneId, owner, uri); + } + } else { + if (!bypassVlanOverlapCheck && networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), GuestType.Isolated).size() > 0) { + throw new InvalidParameterValueException(String.format( + "There is an existing isolated/shared network that overlaps with vlan id:%s in zone %s", vlanId, zone)); + } + } + } + + protected void validateDedicatedGuestVlanOwnership(String vlanId, long zoneId, Account owner, URI uri) { + final List dcVnets = dataCenterVnetDao.findVnet(zoneId, BroadcastDomainType.getValue(uri)); + //for the network that is created as part of private gateway, + //the vnet is not coming from the data center vnet table, so the list can be empty + if (!dcVnets.isEmpty()) { + final DataCenterVnetVO dcVnet = dcVnets.get(0); + // Fail network creation if specified vlan is dedicated to a different account + if (dcVnet.getAccountGuestVlanMapId() != null) { + final Long accountGuestVlanMapId = dcVnet.getAccountGuestVlanMapId(); + final AccountGuestVlanMapVO map = accountGuestVlanMapDao.findById(accountGuestVlanMapId); + if (map.getAccountId() != owner.getAccountId()) { + throw new InvalidParameterValueException("Vlan " + vlanId + " is dedicated to a different account"); + } + // Fail network creation if owner has a dedicated range of vlans but the specified vlan belongs to the system pool + } else { + final List maps = accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(owner.getAccountId()); + if (maps != null && !maps.isEmpty()) { + final int vnetsAllocatedToAccount = dataCenterVnetDao.countVnetsAllocatedToAccount(zoneId, owner.getAccountId()); + final int vnetsDedicatedToAccount = dataCenterVnetDao.countVnetsDedicatedToAccount(zoneId, owner.getAccountId()); + if (vnetsAllocatedToAccount < vnetsDedicatedToAccount) { + throw new InvalidParameterValueException("Specified vlan " + vlanId + " doesn't belong" + " to the vlan range dedicated to the owner " + + owner.getAccountName()); + } + } + } + } + } + + protected boolean hasGuestBypassVlanOverlapCheck(final boolean bypassVlanOverlapCheck, final NetworkOfferingVO offering, final boolean isPrivateNetwork) { + return bypassVlanOverlapCheck && (offering.getGuestType() != GuestType.Isolated || isPrivateNetwork); + } + + @Override + public void checkL2OfferingServices(NetworkOfferingVO offering) { + if (offering.getGuestType() == GuestType.L2 && !networkModel.listNetworkOfferingServices(offering.getId()).isEmpty() && + (!networkModel.areServicesSupportedByNetworkOffering(offering.getId(), Service.UserData) || + (networkModel.areServicesSupportedByNetworkOffering(offering.getId(), Service.UserData) && + networkModel.listNetworkOfferingServices(offering.getId()).size() > 1))) { + throw new InvalidParameterValueException("For L2 networks, only UserData service is allowed"); + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java index 4262ee701aab..3cd3d4cb0a83 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -16,14 +16,11 @@ // under the License. package org.apache.cloudstack.engine.orchestration; -import static com.cloud.configuration.ConfigurationManager.MESSAGE_DELETE_VLAN_IP_RANGE_EVENT; - import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -32,18 +29,15 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.UUID; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ControlledEntity.ACLType; -import org.apache.cloudstack.annotation.AnnotationService; -import org.apache.cloudstack.annotation.dao.AnnotationDao; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.cloud.entity.api.db.VMNetworkMapVO; @@ -70,31 +64,21 @@ import com.cloud.agent.api.AgentControlAnswer; import com.cloud.agent.api.AgentControlCommand; import com.cloud.agent.api.Answer; -import com.cloud.agent.api.CheckNetworkAnswer; -import com.cloud.agent.api.CheckNetworkCommand; import com.cloud.agent.api.CleanupPersistentNetworkResourceAnswer; import com.cloud.agent.api.CleanupPersistentNetworkResourceCommand; import com.cloud.agent.api.Command; -import com.cloud.agent.api.SetupPersistentNetworkAnswer; -import com.cloud.agent.api.SetupPersistentNetworkCommand; import com.cloud.agent.api.StartupCommand; -import com.cloud.agent.api.StartupRoutingCommand; -import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.deployasis.OVFNetworkTO; import com.cloud.alert.AlertManager; -import com.cloud.api.query.dao.DomainRouterJoinDao; -import com.cloud.api.query.vo.DomainRouterJoinVO; import com.cloud.bgp.BGPService; import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.ASNumberVO; -import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; import com.cloud.dc.DataCenterVO; -import com.cloud.dc.DataCenterVnetVO; import com.cloud.dc.PodVlanMapVO; import com.cloud.dc.Vlan; import com.cloud.dc.VlanDetailsVO; @@ -110,10 +94,8 @@ import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; import com.cloud.deployasis.dao.TemplateDeployAsIsDetailsDao; -import com.cloud.domain.Domain; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; -import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.ConnectionException; import com.cloud.exception.InsufficientAddressCapacityException; @@ -123,13 +105,11 @@ import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.UnsupportedServiceException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.network.IpAddress; import com.cloud.network.IpAddressManager; import com.cloud.network.Ipv6Service; import com.cloud.network.Network; @@ -138,22 +118,14 @@ import com.cloud.network.Network.GuestType; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; -import com.cloud.network.NetworkMigrationResponder; import com.cloud.network.NetworkModel; import com.cloud.network.NetworkProfile; -import com.cloud.network.NetworkService; import com.cloud.network.NetworkStateListener; -import com.cloud.network.Networks; import com.cloud.network.Networks.BroadcastDomainType; import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetwork; -import com.cloud.network.PhysicalNetworkSetupInfo; -import com.cloud.network.RemoteAccessVpn; import com.cloud.network.VpcVirtualNetworkApplianceService; -import com.cloud.network.addr.PublicIp; import com.cloud.network.dao.AccountGuestVlanMapDao; -import com.cloud.network.dao.AccountGuestVlanMapVO; -import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; @@ -166,16 +138,12 @@ import com.cloud.network.dao.NetworkDomainDao; import com.cloud.network.dao.NetworkDomainVO; import com.cloud.network.dao.NetworkServiceMapDao; -import com.cloud.network.dao.NetworkServiceMapVO; import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao; -import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO; -import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.network.dao.RemoteAccessVpnDao; -import com.cloud.network.dao.RemoteAccessVpnVO; import com.cloud.network.dao.RouterNetworkDao; import com.cloud.network.element.AggregatedCommandExecutor; import com.cloud.network.element.ConfigDriveNetworkElement; @@ -184,33 +152,19 @@ import com.cloud.network.element.IpDeployer; import com.cloud.network.element.LoadBalancingServiceProvider; import com.cloud.network.element.NetworkElement; -import com.cloud.network.element.RedundantResource; import com.cloud.network.element.StaticNatServiceProvider; import com.cloud.network.element.UserDataServiceProvider; import com.cloud.network.element.VirtualRouterElement; import com.cloud.network.guru.NetworkGuru; import com.cloud.network.guru.NetworkGuruAdditionalFunctions; -import com.cloud.network.lb.LoadBalancingRulesManager; import com.cloud.network.router.VirtualRouter; -import com.cloud.network.rules.FirewallManager; -import com.cloud.network.rules.FirewallRule; -import com.cloud.network.rules.FirewallRule.Purpose; -import com.cloud.network.rules.FirewallRuleVO; import com.cloud.network.rules.LoadBalancerContainer.Scheme; -import com.cloud.network.rules.PortForwardingRuleVO; -import com.cloud.network.rules.RulesManager; -import com.cloud.network.rules.StaticNatRule; -import com.cloud.network.rules.StaticNatRuleImpl; -import com.cloud.network.rules.dao.PortForwardingRulesDao; -import com.cloud.network.vpc.NetworkACLManager; import com.cloud.network.vpc.Vpc; import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcVO; -import com.cloud.network.vpc.dao.PrivateIpDao; import com.cloud.network.vpn.RemoteAccessVpnService; import com.cloud.offering.NetworkOffering; import com.cloud.offering.NetworkOffering.Availability; -import com.cloud.offerings.NetworkOfferingServiceMapVO; import com.cloud.offerings.NetworkOfferingVO; import com.cloud.offerings.dao.NetworkOfferingDao; import com.cloud.offerings.dao.NetworkOfferingDetailsDao; @@ -224,7 +178,6 @@ import com.cloud.user.dao.AccountDao; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; -import com.cloud.utils.UuidUtils; import com.cloud.utils.component.AdapterBase; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; @@ -243,12 +196,9 @@ import com.cloud.utils.fsm.NoTransitionException; import com.cloud.utils.fsm.StateMachine2; import com.cloud.utils.net.Dhcp; -import com.cloud.utils.net.NetUtils; import com.cloud.vm.DomainRouterVO; import com.cloud.vm.Nic; -import com.cloud.vm.Nic.ReservationStrategy; import com.cloud.vm.NicExtraDhcpOptionVO; -import com.cloud.vm.NicIpAlias; import com.cloud.vm.NicProfile; import com.cloud.vm.NicVO; import com.cloud.vm.ReservationContext; @@ -264,9 +214,7 @@ import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.NicExtraDhcpOptionDao; import com.cloud.vm.dao.NicIpAliasDao; -import com.cloud.vm.dao.NicIpAliasVO; import com.cloud.vm.dao.NicSecondaryIpDao; -import com.cloud.vm.dao.NicSecondaryIpVO; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.googlecode.ipv6.IPv6Address; @@ -303,10 +251,6 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject NicDao _nicDao; @Inject - RulesManager _rulesMgr; - @Inject - LoadBalancingRulesManager _lbMgr; - @Inject RemoteAccessVpnService _vpnMgr; @Inject PodVlanMapDao _podVlanMapDao; @@ -333,8 +277,6 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject DomainRouterDao routerDao; @Inject - DomainRouterJoinDao routerJoinDao; - @Inject RemoteAccessVpnDao _remoteAccessVpnDao; @Inject VpcVirtualNetworkApplianceService _routerService; @@ -345,8 +287,6 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject ResourceManager resourceManager; @Inject - private AnnotationDao annotationDao; - @Inject public ManagementServer mgr; @Inject NetworkPermissionDao networkPermissionDao; @@ -414,10 +354,6 @@ public void setDhcpProviders(final List dhcpProviders) { @Inject VMInstanceDao _vmDao; @Inject - FirewallManager _firewallMgr; - @Inject - FirewallRulesDao _firewallDao; - @Inject ResourceLimitService _resourceLimitMgr; @Inject @@ -427,8 +363,6 @@ public void setDhcpProviders(final List dhcpProviders) { @Inject PhysicalNetworkServiceProviderDao _pNSPDao; @Inject - PortForwardingRulesDao _portForwardingRulesDao; - @Inject PhysicalNetworkTrafficTypeDao _pNTrafficTypeDao; @Inject AgentManager _agentMgr; @@ -439,11 +373,47 @@ public void setDhcpProviders(final List dhcpProviders) { @Inject VpcManager _vpcMgr; @Inject - PrivateIpDao _privateIpDao; + NetworkModel _networkModel; @Inject - NetworkACLManager _networkACLMgr; + NicProfileLifecycleMappingService nicProfileLifecycleMappingService; @Inject - NetworkModel _networkModel; + RequestedNicIpReservationService requestedNicIpReservationService; + @Inject + NetworkProviderResolutionService networkProviderResolutionService; + @Inject + NetworkProviderMappingService networkProviderMappingService; + @Inject + NicDhcpCleanupService nicDhcpCleanupService; + @Inject + NicElementPreparationService nicElementPreparationService; + @Inject + NicProfileMtuService nicProfileMtuService; + @Inject + NicImportService nicImportService; + @Inject + NicMigrationService nicMigrationService; + @Inject + NicAuxiliaryService nicAuxiliaryService; + @Inject + NetworkHostSetupService networkHostSetupService; + @Inject + NetworkUpdateSequenceService networkUpdateSequenceService; + @Inject + NetworkServiceChangeCleanupService networkServiceChangeCleanupService; + @Inject + NetworkRuleReprogrammingService networkRuleReprogrammingService; + @Inject + RouterDefaultDnsUpdateService routerDefaultDnsUpdateService; + @Inject + NetworkResourceCleanupService networkResourceCleanupService; + @Inject + PersistentNetworkSetupService persistentNetworkSetupService; + @Inject + NetworkOfferingVlanValidationService networkOfferingVlanValidationService; + @Inject + GuestNetworkCreationPreparationService guestNetworkCreationPreparationService; + @Inject + NetworkVlanRangeCleanupService networkVlanRangeCleanupService; @Inject NicSecondaryIpDao _nicSecondaryIpDao; @Inject @@ -461,38 +431,6 @@ public void setDhcpProviders(final List dhcpProviders) { HashMap _lastNetworkIdsToFree = new HashMap<>(); - private void updateRouterDefaultDns(final VirtualMachineProfile vmProfile, final NicProfile nicProfile) { - if (!Type.DomainRouter.equals(vmProfile.getType()) || !nicProfile.isDefaultNic()) { - return; - } - DomainRouterVO router = routerDao.findById(vmProfile.getId()); - if (router != null && router.getVpcId() != null) { - final Vpc vpc = _vpcMgr.getActiveVpc(router.getVpcId()); - if (StringUtils.isNotBlank(vpc.getIp4Dns1())) { - nicProfile.setIPv4Dns1(vpc.getIp4Dns1()); - nicProfile.setIPv4Dns2(vpc.getIp4Dns2()); - } - if (StringUtils.isNotBlank(vpc.getIp6Dns1())) { - nicProfile.setIPv6Dns1(vpc.getIp6Dns1()); - nicProfile.setIPv6Dns2(vpc.getIp6Dns2()); - } - return; - } - List networkIds = routerNetworkDao.getRouterNetworks(vmProfile.getId()); - if (CollectionUtils.isEmpty(networkIds) || networkIds.size() > 1) { - return; - } - final NetworkVO routerNetwork = _networksDao.findById(networkIds.get(0)); - if (StringUtils.isNotBlank(routerNetwork.getDns1())) { - nicProfile.setIPv4Dns1(routerNetwork.getDns1()); - nicProfile.setIPv4Dns2(routerNetwork.getDns2()); - } - if (StringUtils.isNotBlank(routerNetwork.getIp6Dns1())) { - nicProfile.setIPv6Dns1(routerNetwork.getIp6Dns1()); - nicProfile.setIPv6Dns2(routerNetwork.getIp6Dns2()); - } - } - @Override @DB public boolean configure(final String name, final Map params) throws ConfigurationException { @@ -1157,10 +1095,10 @@ public Pair allocateNic(final NicProfile requested, final N final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network)); if (vm.getType() == Type.DomainRouter) { - Pair networks = getGuestNetworkRouterAndVpcDetails(vm.getId()); - setMtuDetailsInVRNic(networks, network, vo); + Pair networks = nicProfileMtuService.getGuestNetworkRouterAndVpcDetails(vm.getId()); + nicProfileMtuService.setMtuDetailsInVRNic(networks, network, vo); _nicDao.update(vo.getId(), vo); - setMtuInVRNicProfile(networks, network.getTrafficType(), vmNic); + nicProfileMtuService.setMtuInVRNicProfile(networks, network.getTrafficType(), vmNic); } return new Pair<>(vmNic, Integer.valueOf(deviceId)); } @@ -1199,233 +1137,32 @@ private boolean isNicAllocatedForProviderPublicNetworkOnVR(Network network, NicP return isForProvider && !ip.isForSystemVms(); } - private void setMtuDetailsInVRNic(final Pair networks, Network network, NicVO vo) { - if (TrafficType.Public == network.getTrafficType()) { - if (networks == null) { - return; - } - NetworkVO networkVO = networks.first(); - VpcVO vpcVO = networks.second(); - if (vpcVO != null) { - vo.setMtu(vpcVO.getPublicMtu()); - } else { - vo.setMtu(networkVO.getPublicMtu()); - } - } else if (TrafficType.Guest == network.getTrafficType()) { - vo.setMtu(network.getPrivateMtu()); - } - } - - private void setMtuInVRNicProfile(final Pair networks, TrafficType trafficType, NicProfile vmNic) { - if (networks == null) { - return; - } - NetworkVO networkVO = networks.first(); - VpcVO vpcVO = networks.second(); - if (networkVO != null) { - if (TrafficType.Public == trafficType) { - if (vpcVO != null) { - vmNic.setMtu(vpcVO.getPublicMtu()); - } else { - vmNic.setMtu(networkVO.getPublicMtu()); - } - } else if (TrafficType.Guest == trafficType) { - vmNic.setMtu(networkVO.getPrivateMtu()); - } - } - } - - private Pair getGuestNetworkRouterAndVpcDetails(long routerId) { - List routerVo = routerJoinDao.getRouterByIdAndTrafficType(routerId, TrafficType.Guest); - if (routerVo.isEmpty()) { - routerVo = routerJoinDao.getRouterByIdAndTrafficType(routerId, TrafficType.Public); - if (routerVo.isEmpty()) { - return null; - } - } - DomainRouterJoinVO guestRouterDetails = routerVo.get(0); - VpcVO vpc = null; - if (guestRouterDetails.getVpcId() != 0) { - vpc = _entityMgr.findById(VpcVO.class, guestRouterDetails.getVpcId()); - } - long networkId = guestRouterDetails.getNetworkId(); - return new Pair<>(_networksDao.findById(networkId), vpc); - } - - /** - * If the requested IPv4 address from the NicProfile was configured then it configures the IPv4 address, Netmask and Gateway to deploy the VM with the requested IP. - */ protected void configureNicProfileBasedOnRequestedIp(NicProfile requestedNicProfile, NicProfile nicProfile, Network network) { - if (requestedNicProfile == null) { - return; - } - String requestedIpv4Address = requestedNicProfile.getRequestedIPv4(); - if (requestedIpv4Address == null) { - return; - } - if (!NetUtils.isValidIp4(requestedIpv4Address)) { - throw new InvalidParameterValueException(String.format("The requested [IPv4 address='%s'] is not a valid IP address", requestedIpv4Address)); - } - - VlanVO vlanVo = _vlanDao.findByNetworkIdAndIpv4(network.getId(), requestedIpv4Address); - if (vlanVo == null) { - throw new InvalidParameterValueException(String.format("Trying to configure a Nic with the requested [IPv4='%s'] but cannot find a Vlan for the [network '%s']", - requestedIpv4Address, network)); - } - - String ipv4Gateway = vlanVo.getVlanGateway(); - String ipv4Netmask = vlanVo.getVlanNetmask(); - - if (!NetUtils.isValidIp4(ipv4Gateway)) { - throw new InvalidParameterValueException(String.format("The [IPv4Gateway='%s'] from [Vlan id=%d uuid=%s] is not valid", ipv4Gateway, vlanVo.getId(), vlanVo.getUuid())); - } - if (!NetUtils.isValidIp4Netmask(ipv4Netmask)) { - throw new InvalidParameterValueException(String.format("The [IPv4Netmask='%s'] from [Vlan id=%d uuid=%s] is not valid", ipv4Netmask, vlanVo.getId(), vlanVo.getUuid())); - } - - acquireLockAndCheckIfIpv4IsFree(network, requestedIpv4Address); - - nicProfile.setIPv4Address(requestedIpv4Address); - nicProfile.setIPv4Gateway(ipv4Gateway); - nicProfile.setIPv4Netmask(ipv4Netmask); - - if (nicProfile.getMacAddress() == null || !_networkModel.isMACUnique(nicProfile.getMacAddress(), network.getId())) { - try { - String macAddress = _networkModel.getNextAvailableMacAddressInNetwork(network.getId()); - nicProfile.setMacAddress(macAddress); - } catch (InsufficientAddressCapacityException e) { - throw new CloudRuntimeException(String.format("Cannot get next available mac address in [network %s]", network), e); - } - } + requestedNicIpReservationService.configureNicProfileBasedOnRequestedIp(requestedNicProfile, nicProfile, network); } - /** - * Acquires lock in "user_ip_address" and checks if the requested IPv4 address is Free. - */ protected void acquireLockAndCheckIfIpv4IsFree(Network network, String requestedIpv4Address) { - IPAddressVO ipVO = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), requestedIpv4Address); - if (ipVO == null) { - throw new InvalidParameterValueException( - String.format("Cannot find IPAddressVO for guest [IPv4 address='%s'] and [network %s]", requestedIpv4Address, network)); - } - try { - IPAddressVO lockedIpVO = _ipAddressDao.acquireInLockTable(ipVO.getId()); - validateLockedRequestedIp(ipVO, lockedIpVO); - lockedIpVO.setState(IPAddressVO.State.Allocated); - lockedIpVO.setAllocatedTime(new Date()); - _ipAddressDao.update(lockedIpVO.getId(), lockedIpVO); - } finally { - _ipAddressDao.releaseFromLockTable(ipVO.getId()); - } + requestedNicIpReservationService.acquireLockAndCheckIfIpv4IsFree(network, requestedIpv4Address); } - /** - * Validates the locked IP, throwing an exception if the locked IP is null or the locked IP is not in 'Free' state. - */ protected void validateLockedRequestedIp(IPAddressVO ipVO, IPAddressVO lockedIpVO) { - if (lockedIpVO == null) { - throw new InvalidParameterValueException(String.format("Cannot acquire guest [IPv4 address='%s'] as it was removed while acquiring lock", ipVO.getAddress())); - } - if (lockedIpVO.getState() != IPAddressVO.State.Free) { - throw new InvalidParameterValueException( - String.format("Cannot acquire guest [IPv4 address='%s']; The Ip address is in [state='%s']", ipVO.getAddress(), lockedIpVO.getState().toString())); - } + requestedNicIpReservationService.validateLockedRequestedIp(ipVO, lockedIpVO); } protected Integer applyProfileToNic(final NicVO vo, final NicProfile profile, Integer deviceId) { - if (profile.getDeviceId() != null) { - vo.setDeviceId(profile.getDeviceId()); - } else if (deviceId != null) { - vo.setDeviceId(deviceId++); - } - - if (profile.getReservationStrategy() != null) { - vo.setReservationStrategy(profile.getReservationStrategy()); - } - - vo.setDefaultNic(profile.isDefaultNic()); - - vo.setIPv4Address(profile.getIPv4Address()); - vo.setAddressFormat(profile.getFormat()); - - if (profile.getMacAddress() != null) { - vo.setMacAddress(profile.getMacAddress()); - } - - vo.setMode(profile.getMode()); - vo.setIPv4Netmask(profile.getIPv4Netmask()); - vo.setIPv4Gateway(profile.getIPv4Gateway()); - - if (profile.getBroadCastUri() != null) { - vo.setBroadcastUri(profile.getBroadCastUri()); - } - - if (profile.getIsolationUri() != null) { - vo.setIsolationUri(profile.getIsolationUri()); - } - - vo.setState(Nic.State.Allocated); - - vo.setIPv6Address(profile.getIPv6Address()); - vo.setIPv6Gateway(profile.getIPv6Gateway()); - vo.setIPv6Cidr(profile.getIPv6Cidr()); - - return deviceId; + return nicProfileLifecycleMappingService.applyProfileToNic(vo, profile, deviceId); } protected void applyProfileToNicForRelease(final NicVO vo, final NicProfile profile) { - vo.setIPv4Gateway(profile.getIPv4Gateway()); - vo.setAddressFormat(profile.getFormat()); - vo.setIPv4Address(profile.getIPv4Address()); - vo.setIPv6Address(profile.getIPv6Address()); - vo.setMacAddress(profile.getMacAddress()); - if (profile.getReservationStrategy() != null) { - vo.setReservationStrategy(profile.getReservationStrategy()); - } - vo.setBroadcastUri(profile.getBroadCastUri()); - vo.setIsolationUri(profile.getIsolationUri()); - vo.setIPv4Netmask(profile.getIPv4Netmask()); + nicProfileLifecycleMappingService.applyProfileToNicForRelease(vo, profile); } protected void applyProfileToNetwork(final NetworkVO network, final NetworkProfile profile) { - network.setBroadcastUri(profile.getBroadcastUri()); - network.setDns1(profile.getDns1()); - network.setDns2(profile.getDns2()); - network.setPhysicalNetworkId(profile.getPhysicalNetworkId()); + nicProfileLifecycleMappingService.applyProfileToNetwork(network, profile); } protected NicTO toNicTO(final NicVO nic, final NicProfile profile, final NetworkVO config) { - final NicTO to = new NicTO(); - to.setDeviceId(nic.getDeviceId()); - to.setBroadcastType(config.getBroadcastDomainType()); - to.setType(config.getTrafficType()); - to.setIp(nic.getIPv4Address()); - to.setNetmask(nic.getIPv4Netmask()); - to.setMac(nic.getMacAddress()); - to.setDns1(profile.getIPv4Dns1()); - to.setDns2(profile.getIPv4Dns2()); - if (nic.getIPv4Gateway() != null) { - to.setGateway(nic.getIPv4Gateway()); - } else { - to.setGateway(config.getGateway()); - } - if (nic.getVmType() != VirtualMachine.Type.User) { - to.setPxeDisable(true); - } - to.setDefaultNic(nic.isDefaultNic()); - to.setBroadcastUri(nic.getBroadcastUri()); - to.setIsolationuri(nic.getIsolationUri()); - if (profile != null) { - to.setDns1(profile.getIPv4Dns1()); - to.setDns2(profile.getIPv4Dns2()); - } - - final Integer networkRate = _networkModel.getNetworkRate(config.getId(), null); - to.setNetworkRateMbps(networkRate); - - to.setUuid(config.getUuid()); - - return to; + return nicProfileLifecycleMappingService.toNicTO(nic, profile, config); } boolean isNetworkImplemented(final NetworkVO network) { @@ -1476,59 +1213,6 @@ private NicTO createNicTOFromNetworkAndOffering(NetworkVO networkVO, NetworkOffe return to; } - private Pair isNtwConfiguredInCluster(HostVO hostVO, Map> clusterToHostsMap, NetworkVO networkVO, NetworkOfferingVO networkOfferingVO) { - Long clusterId = hostVO.getClusterId(); - List hosts = clusterToHostsMap.get(clusterId); - if (hosts == null) { - hosts = new ArrayList<>(); - } - if (hostVO.getHypervisorType() == HypervisorType.KVM || hostVO.getHypervisorType() == HypervisorType.XenServer) { - hosts.add(hostVO.getId()); - clusterToHostsMap.put(clusterId, hosts); - return new Pair<>(false, createNicTOFromNetworkAndOffering(networkVO, networkOfferingVO, hostVO)); - } - if (hosts != null && !hosts.isEmpty()) { - return new Pair<>(true, createNicTOFromNetworkAndOffering(networkVO, networkOfferingVO, hostVO)); - } - hosts.add(hostVO.getId()); - clusterToHostsMap.put(clusterId, hosts); - return new Pair<>(false, createNicTOFromNetworkAndOffering(networkVO, networkOfferingVO, hostVO)); - } - - private void setupPersistentNetwork(NetworkVO network, NetworkOfferingVO offering, Long dcId) throws AgentUnavailableException, OperationTimedoutException { - List clusterVOs = clusterDao.listClustersByDcId(dcId); - List hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, dcId); - Map> clusterToHostsMap = new HashMap<>(); - - for (HostVO host : hosts) { - try { - Pair networkCfgStateAndDetails = isNtwConfiguredInCluster(host, clusterToHostsMap, network, offering); - if (networkCfgStateAndDetails.first()) { - continue; - } - NicTO to = networkCfgStateAndDetails.second(); - SetupPersistentNetworkCommand cmd = new SetupPersistentNetworkCommand(to); - final SetupPersistentNetworkAnswer answer = (SetupPersistentNetworkAnswer) _agentMgr.send(host.getId(), cmd); - - if (answer == null) { - logger.warn("Unable to get an answer to the SetupPersistentNetworkCommand from agent: {}", host); - clusterToHostsMap.get(host.getClusterId()).remove(host.getId()); - continue; - } - - if (!answer.getResult()) { - logger.warn("Unable to setup agent {} due to {}", host, answer.getDetails()); - clusterToHostsMap.get(host.getClusterId()).remove(host.getId()); - } - } catch (Exception e) { - logger.warn("Failed to connect to host: {}", host); - } - } - if (clusterToHostsMap.keySet().size() != clusterVOs.size()) { - logger.warn("Hosts on all clusters may not have been configured with network devices."); - } - } - private boolean networkMeetsPersistenceCriteria(NetworkVO network, NetworkOfferingVO offering, boolean cleanup) { boolean criteriaMet = offering.isPersistent() && (network.getBroadcastUri() != null && BroadcastDomainType.getSchemeValue(network.getBroadcastUri()) == BroadcastDomainType.Vlan); @@ -1595,7 +1279,7 @@ public Pair implementNetwork(final long networkId, final long dcId = dest.getDataCenter().getId(); if (networkMeetsPersistenceCriteria(network, offering, false)) { - setupPersistentNetwork(network, offering, dcId); + persistentNetworkSetupService.setupPersistentNetwork(network, offering, dcId); } if (isSharedNetworkWithServices(network)) { network.setState(Network.State.Implemented); @@ -1762,298 +1446,32 @@ private void implementNetworkElements(final DeployDestination dest, final Reserv // This method re-programs the rules/ips for existing network protected boolean reprogramNetworkRules(final long networkId, final Account caller, final Network network) throws ResourceUnavailableException { - boolean success = true; - - //Apply egress rules first to effect the egress policy early on the guest traffic - final List firewallEgressRulesToApply = _firewallDao.listByNetworkPurposeTrafficType(networkId, Purpose.Firewall, FirewallRule.TrafficType.Egress); - final NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId()); - final DataCenter zone = _dcDao.findById(network.getDataCenterId()); - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Firewall) && _networkModel.areServicesSupportedInNetwork(network.getId(), Service.Firewall) - && (network.getGuestType() == Network.GuestType.Isolated || network.getGuestType() == Network.GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) { - // add default egress rule to accept the traffic - _firewallMgr.applyDefaultEgressFirewallRule(network.getId(), offering.isEgressDefaultPolicy(), true); - } - if (!_firewallMgr.applyFirewallRules(firewallEgressRulesToApply, false, caller)) { - logger.warn("Failed to reapply firewall Egress rule(s) as a part of Network {} restart", network); - success = false; - } - - // associate all ip addresses - if (!_ipAddrMgr.applyIpAssociations(network, false)) { - logger.warn("Failed to apply IP addresses as a part of Network {} restart", network); - success = false; - } - - // apply BGP settings - if (!bgpService.applyBgpPeers(network, false)) { - logger.warn("Failed to apply bpg peers as a part of network {} restart", network); - success = false; - } - - - // apply static nat - if (!_rulesMgr.applyStaticNatsForNetwork(network, false, caller)) { - logger.warn("Failed to apply static nats a part of network {} restart", network); - success = false; - } - - // apply firewall rules - final List firewallIngressRulesToApply = _firewallDao.listByNetworkPurposeTrafficType(networkId, Purpose.Firewall, FirewallRule.TrafficType.Ingress); - if (!_firewallMgr.applyFirewallRules(firewallIngressRulesToApply, false, caller)) { - logger.warn("Failed to reapply Ingress firewall rule(s) as a part of network {} restart", network); - success = false; - } - - // apply port forwarding rules - if (!_rulesMgr.applyPortForwardingRulesForNetwork(networkId, false, caller)) { - logger.warn("Failed to reapply port forwarding rule(s) as a part of network {} restart", network); - success = false; - } - - // apply static nat rules - if (!_rulesMgr.applyStaticNatRulesForNetwork(networkId, false, caller)) { - logger.warn("Failed to reapply static nat rule(s) as a part of network {} restart", network); - success = false; - } - - // apply public load balancer rules - if (!_lbMgr.applyLoadBalancersForNetwork(network, Scheme.Public)) { - logger.warn("Failed to reapply Public load balancer rules as a part of network {} restart", network); - success = false; - } - - // apply internal load balancer rules - if (!_lbMgr.applyLoadBalancersForNetwork(network, Scheme.Internal)) { - logger.warn("Failed to reapply internal load balancer rules as a part of network {} restart", network); - success = false; - } - - // apply vpn rules - final List vpnsToReapply = _vpnMgr.listRemoteAccessVpns(networkId); - if (vpnsToReapply != null) { - for (final RemoteAccessVpn vpn : vpnsToReapply) { - // Start remote access vpn per ip - if (_vpnMgr.startRemoteAccessVpn(vpn.getServerAddressId(), false) == null) { - logger.warn("Failed to reapply vpn rules as a part of network {} restart", network); - success = false; - } - } - } - - //apply network ACLs - if (!_networkACLMgr.applyACLToNetwork(networkId)) { - logger.warn("Failed to reapply network ACLs as a part of of network {}", network); - success = false; - } - - return success; - } - - protected boolean prepareElement(final NetworkElement element, final Network network, final NicProfile profile, final VirtualMachineProfile vmProfile, final DeployDestination dest, - final ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { - element.prepare(network, profile, vmProfile, dest, context); - if (vmProfile.getType() == Type.User && element.getProvider() != null) { - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) - && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider()) && element instanceof DhcpServiceProvider) { - final DhcpServiceProvider sp = (DhcpServiceProvider) element; - if (isDhcpAccrossMultipleSubnetsSupported(sp)) { - if (!sp.configDhcpSupportForSubnet(network, profile, vmProfile, dest, context)) { - return false; - } - } - if (!sp.addDhcpEntry(network, profile, vmProfile, dest, context)) { - return false; - } - } - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) - && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) && element instanceof DnsServiceProvider) { - final DnsServiceProvider sp = (DnsServiceProvider) element; - if (profile.getIPv6Address() == null) { - if (!sp.configDnsSupportForSubnet(network, profile, vmProfile, dest, context)) { - return false; - } - } - if (!sp.addDnsEntry(network, profile, vmProfile, dest, context)) { - return false; - } - } - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) - && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.UserData, element.getProvider()) && element instanceof UserDataServiceProvider) { - final UserDataServiceProvider sp = (UserDataServiceProvider) element; - if (!sp.addPasswordAndUserdata(network, profile, vmProfile, dest, context)) { - return false; - } - } - if (element instanceof ConfigDriveNetworkElement && (( - _networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) && - _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider()) - ) || ( - _networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) && - _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) - ) || ( - _networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) && - _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.UserData, element.getProvider()) - ))) { - final ConfigDriveNetworkElement sp = (ConfigDriveNetworkElement) element; - return sp.createConfigDriveIso(profile, vmProfile, dest, null); - } - } - return true; + return networkRuleReprogrammingService.reprogramNetworkRules(networkId, caller, network); } @Override - public boolean canUpdateInSequence(Network network, boolean forced) { - List providers = getNetworkProviders(network.getId()); - - //check if the there are no service provider other than virtualrouter. - for (Provider provider : providers) { - if (provider != Provider.VirtualRouter) - throw new UnsupportedOperationException("Cannot update the network resources in sequence when providers other than virtualrouter are used"); - } - //check if routers are in correct state before proceeding with the update - List routers = routerDao.listByNetworkAndRole(network.getId(), VirtualRouter.Role.VIRTUAL_ROUTER); - for (DomainRouterVO router : routers){ - if (router.getRedundantState() == VirtualRouter.RedundantState.UNKNOWN) { - if (!forced) { - throw new CloudRuntimeException("Domain router: " + router.getInstanceName() + " is in unknown state, Cannot update network. set parameter forced to true for forcing an update"); - } - } - } - return true; + public boolean canUpdateInSequence(final Network network, final boolean forced) { + return networkUpdateSequenceService.canUpdateInSequence(network, forced); } @Override - public List getServicesNotSupportedInNewOffering(Network network, long newNetworkOfferingId) { - NetworkOffering offering = _networkOfferingDao.findById(newNetworkOfferingId); - List services = _ntwkOfferingSrvcDao.listServicesForNetworkOffering(offering.getId()); - List serviceMap = _ntwkSrvcDao.getServicesInNetwork(network.getId()); - List servicesNotInNewOffering = new ArrayList<>(); - for (NetworkServiceMapVO serviceVO : serviceMap) { - boolean inlist = false; - for (String service : services) { - if (serviceVO.getService().equalsIgnoreCase(service)) { - inlist = true; - break; - } - } - if (!inlist) { - //ignore Gateway service as this has no effect on the - //behaviour of network. - if (!serviceVO.getService().equalsIgnoreCase(Service.Gateway.getName())) - servicesNotInNewOffering.add(serviceVO.getService()); - } - } - return servicesNotInNewOffering; + public List getServicesNotSupportedInNewOffering(final Network network, final long newNetworkOfferingId) { + return networkServiceChangeCleanupService.getServicesNotSupportedInNewOffering(network, newNetworkOfferingId); } @Override - public void cleanupConfigForServicesInNetwork(List services, final Network network) { - long networkId = network.getId(); - Account caller = _accountDao.findById(Account.ACCOUNT_ID_SYSTEM); - long userId = User.UID_SYSTEM; - //remove all PF/Static Nat rules for the network - logger.info("Services: {} are no longer supported in network: {} after applying new network offering: {} removing the related configuration", - services::toString, network::toString, () -> _networkOfferingDao.findById(network.getNetworkOfferingId())); - if (services.contains(Service.StaticNat.getName()) || services.contains(Service.PortForwarding.getName())) { - try { - if (_rulesMgr.revokeAllPFStaticNatRulesForNetwork(networkId, userId, caller)) { - logger.debug("Successfully cleaned up portForwarding/staticNat rules for network {}", network); - } else { - logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup", network); - } - if (services.contains(Service.StaticNat.getName())) { - //removing static nat configured on ips. - //optimizing the db operations using transaction. - Transaction.execute(new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - List ips = _ipAddressDao.listStaticNatPublicIps(network.getId()); - for (IPAddressVO ip : ips) { - ip.setOneToOneNat(false); - ip.setAssociatedWithVmId(null); - ip.setVmIp(null); - ip.setForRouter(false); - _ipAddressDao.update(ip.getId(), ip); - } - } - }); - } - } catch (ResourceUnavailableException ex) { - logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup due to resourceUnavailable", network, ex); - } - } - if (services.contains(Service.SourceNat.getName())) { - Transaction.execute(new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - List ips = _ipAddressDao.listByAssociatedNetwork(network.getId(), true); - //removing static nat configured on ips. - for (IPAddressVO ip : ips) { - ip.setSourceNat(false); - _ipAddressDao.update(ip.getId(), ip); - } - } - }); - } - if (services.contains(Service.Lb.getName())) { - //remove all LB rules for the network - if (_lbMgr.removeAllLoadBalanacersForNetwork(networkId, caller, userId)) { - logger.debug("Successfully cleaned up load balancing rules for network {}", network); - } else { - logger.warn("Failed to cleanup LB rules as a part of network {} cleanup", network); - } - } - - if (services.contains(Service.Firewall.getName())) { - //revoke all firewall rules for the network - try { - if (_firewallMgr.revokeAllFirewallRulesForNetwork(network, userId, caller)) { - logger.debug("Successfully cleaned up firewallRules rules for network {}", network); - } else { - logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup", network); - } - } catch (ResourceUnavailableException ex) { - logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup due to resourceUnavailable", network, ex); - } - } - - //do not remove vpn service for vpc networks. - if (services.contains(Service.Vpn.getName()) && network.getVpcId() == null) { - RemoteAccessVpnVO vpn = _remoteAccessVpnDao.findByAccountAndNetwork(network.getAccountId(), networkId); - try { - _vpnMgr.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller, true); - } catch (ResourceUnavailableException ex) { - logger.warn("Failed to cleanup remote access vpn resources of network: {} due to Exception: {}", network, ex); - } - } + public void cleanupConfigForServicesInNetwork(final List services, final Network network) { + networkServiceChangeCleanupService.cleanupConfigForServicesInNetwork(services, network); } @Override - public void configureUpdateInSequence(Network network) { - List providers = getNetworkProviders(network.getId()); - for (NetworkElement element : networkElements) { - if (providers.contains(element.getProvider())) { - if (element instanceof RedundantResource) { - ((RedundantResource) element).configureResource(network); - } - } - } + public void configureUpdateInSequence(final Network network) { + networkUpdateSequenceService.configureUpdateInSequence(network); } @Override - public int getResourceCount(Network network) { - List providers = getNetworkProviders(network.getId()); - int resourceCount = 0; - for (NetworkElement element : networkElements) { - if (providers.contains(element.getProvider())) { - //currently only one element implements the redundant resource interface - if (element instanceof RedundantResource) { - resourceCount = ((RedundantResource) element).getResourceCount(network); - break; - } - } - } - return resourceCount; + public int getResourceCount(final Network network) { + return networkUpdateSequenceService.getResourceCount(network); } @Override @@ -2073,17 +1491,8 @@ public void configureExtraDhcpOptions(Network network, long nicId) { } @Override - public void finalizeUpdateInSequence(Network network, boolean success) { - List providers = getNetworkProviders(network.getId()); - for (NetworkElement element : networkElements) { - if (providers.contains(element.getProvider())) { - //currently only one element implements the redundant resource interface - if (element instanceof RedundantResource) { - ((RedundantResource) element).finalize(network, success); - break; - } - } - } + public void finalizeUpdateInSequence(final Network network, final boolean success) { + networkUpdateSequenceService.finalizeUpdateInSequence(network, success); } @Override @@ -2168,8 +1577,8 @@ public int compare(final NicVO nic1, final NicVO nic2) { final NetworkVO network = implemented.second(); final NicProfile profile = prepareNic(vmProfile, dest, context, nic.getId(), network); if (vmProfile.getType() == Type.DomainRouter) { - Pair networks = getGuestNetworkRouterAndVpcDetails(vmProfile.getId()); - setMtuInVRNicProfile(networks, network.getTrafficType(), profile); + Pair networks = nicProfileMtuService.getGuestNetworkRouterAndVpcDetails(vmProfile.getId()); + nicProfileMtuService.setMtuInVRNicProfile(networks, network.getTrafficType(), profile); } vmProfile.addNic(profile); } @@ -2224,8 +1633,8 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy } if (vmProfile.getType() == Type.DomainRouter) { - Pair networks = getGuestNetworkRouterAndVpcDetails(vmProfile.getId()); - setMtuDetailsInVRNic(networks, network, nic); + Pair networks = nicProfileMtuService.getGuestNetworkRouterAndVpcDetails(vmProfile.getId()); + nicProfileMtuService.setMtuDetailsInVRNic(networks, network, nic); } updateNic(nic, network, 1); @@ -2237,7 +1646,7 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy + network.getPhysicalNetworkId()); } logger.debug("Asking {} to prepare for {}", element.getName(), nic); - if (!prepareElement(element, network, profile, vmProfile, dest, context)) { + if (!nicElementPreparationService.prepareElement(element, network, profile, vmProfile, dest, context)) { throw new InsufficientAddressCapacityException("unable to configure the dhcp service, due to insufficiant address capacity", Network.class, network.getId()); } } @@ -2245,7 +1654,7 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy profile.setSecurityGroupEnabled(_networkModel.isSecurityGroupSupportedInNetwork(network)); guru.updateNicProfile(profile, network); - updateRouterDefaultDns(vmProfile, profile); + routerDefaultDnsUpdateService.updateRouterDefaultDns(vmProfile, profile); configureExtraDhcpOptions(network, nicId); return profile; } @@ -2260,47 +1669,7 @@ public Map getExtraDhcpOptions(long nicId) { @Override public void prepareNicForMigration(final VirtualMachineProfile vm, final DeployDestination dest) { - if (vm.getType().equals(VirtualMachine.Type.DomainRouter) && (vm.getHypervisorType().equals(HypervisorType.KVM) || vm.getHypervisorType().equals(HypervisorType.VMware))) { - //Include nics hot plugged and not stored in DB - prepareAllNicsForMigration(vm, dest); - return; - } - final List nics = _nicDao.listByVmId(vm.getId()); - final ReservationContext context = new ReservationContextImpl(UUID.randomUUID().toString(), null, null); - for (final NicVO nic : nics) { - final NetworkVO network = _networksDao.findById(nic.getNetworkId()); - final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); - - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), - _networkModel.getNetworkTag(vm.getHypervisorType(), network)); - if (guru instanceof NetworkMigrationResponder) { - if (!((NetworkMigrationResponder) guru).prepareMigration(profile, network, vm, dest, context)) { - logger.error("NetworkGuru {} prepareForMigration failed.", guru); // XXX: Transaction error - } - } - - if (network.getGuestType() == Network.GuestType.L2 && vm.getType() == VirtualMachine.Type.User) { - _userVmMgr.setupVmForPvlan(false, vm.getVirtualMachine().getHostId(), profile); - } - - final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { - if (providersToImplement.contains(element.getProvider())) { - if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " - + network.getPhysicalNetworkId()); - } - if (element instanceof NetworkMigrationResponder) { - if (!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)) { - logger.error("NetworkElement {} prepareForMigration failed.", element); // XXX: Transaction error - } - } - } - } - guru.updateNicProfile(profile, network); - vm.addNic(profile); - } + nicMigrationService.prepareNicForMigration(vm, dest); } /* @@ -2310,155 +1679,17 @@ public void prepareNicForMigration(final VirtualMachineProfile vm, final DeployD */ @Override public void prepareAllNicsForMigration(final VirtualMachineProfile vm, final DeployDestination dest) { - final List nics = _nicDao.listByVmId(vm.getId()); - final ReservationContext context = new ReservationContextImpl(UUID.randomUUID().toString(), null, null); - Long guestNetworkId = null; - for (final NicVO nic : nics) { - final NetworkVO network = _networksDao.findById(nic.getNetworkId()); - if (network.getTrafficType().equals(TrafficType.Guest) && network.getGuestType().equals(GuestType.Isolated)) { - guestNetworkId = network.getId(); - } - final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); - - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, - _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(vm.getHypervisorType(), network)); - if (guru instanceof NetworkMigrationResponder) { - if (!((NetworkMigrationResponder) guru).prepareMigration(profile, network, vm, dest, context)) { - logger.error("NetworkGuru {} prepareForMigration failed.", guru); // XXX: Transaction error - } - } - final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { - if (providersToImplement.contains(element.getProvider())) { - if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s", - element.getProvider().getName(), _physicalNetworkDao.findById(network.getPhysicalNetworkId()))); - } - if (element instanceof NetworkMigrationResponder) { - if (!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)) { - logger.error("NetworkElement {} prepareForMigration failed.", element); // XXX: Transaction error - } - } - } - } - guru.updateNicProfile(profile, network); - vm.addNic(profile); - } - - final List addedURIs = new ArrayList<>(); - if (guestNetworkId != null) { - final List publicIps = _ipAddressDao.listByAssociatedNetwork(guestNetworkId, null); - for (final IPAddressVO userIp : publicIps) { - final PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId())); - final URI broadcastUri = BroadcastDomainType.Vlan.toUri(publicIp.getVlanTag()); - final long ntwkId = publicIp.getNetworkId(); - final Nic nic = _nicDao.findByNetworkIdInstanceIdAndBroadcastUri(ntwkId, vm.getId(), - broadcastUri.toString()); - if (nic == null && !addedURIs.contains(broadcastUri.toString())) { - //Nic details are not available in DB - //Create nic profile for migration - final NetworkVO network = _networksDao.findById(ntwkId); - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NicProfile profile = new NicProfile(); - logger.debug("Creating NIC profile for migration. BroadcastUri: {} NetworkId: {} Instance: {}", broadcastUri.toString(), network, vm); - profile.setDeviceId(255); //dummyId - profile.setIPv4Address(userIp.getAddress().toString()); - profile.setIPv4Netmask(publicIp.getNetmask()); - profile.setIPv4Gateway(publicIp.getGateway()); - profile.setMacAddress(publicIp.getMacAddress()); - profile.setBroadcastType(network.getBroadcastDomainType()); - profile.setTrafficType(network.getTrafficType()); - profile.setBroadcastUri(broadcastUri); - profile.setIsolationUri(Networks.IsolationType.Vlan.toUri(publicIp.getVlanTag())); - profile.setSecurityGroupEnabled(_networkModel.isSecurityGroupSupportedInNetwork(network)); - profile.setName(_networkModel.getNetworkTag(vm.getHypervisorType(), network)); - profile.setNetworkRate(_networkModel.getNetworkRate(network.getId(), vm.getId())); - profile.setNetworkId(network.getId()); - - guru.updateNicProfile(profile, network); - vm.addNic(profile); - addedURIs.add(broadcastUri.toString()); - } - } - } - } - - private NicProfile findNicProfileById(final VirtualMachineProfile vm, final long id) { - for (final NicProfile nic : vm.getNics()) { - if (nic.getId() == id) { - return nic; - } - } - return null; - } + nicMigrationService.prepareAllNicsForMigration(vm, dest); + } @Override public void commitNicForMigration(final VirtualMachineProfile src, final VirtualMachineProfile dst) { - for (final NicProfile nicSrc : src.getNics()) { - final NetworkVO network = _networksDao.findById(nicSrc.getNetworkId()); - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NicProfile nicDst = findNicProfileById(dst, nicSrc.getId()); - final ReservationContext src_context = new ReservationContextImpl(nicSrc.getReservationId(), null, null); - final ReservationContext dst_context = new ReservationContextImpl(nicDst.getReservationId(), null, null); - - if (guru instanceof NetworkMigrationResponder) { - ((NetworkMigrationResponder) guru).commitMigration(nicSrc, network, src, src_context, dst_context); - } - - if (network.getGuestType() == Network.GuestType.L2 && src.getType() == VirtualMachine.Type.User) { - _userVmMgr.setupVmForPvlan(true, src.getVirtualMachine().getHostId(), nicSrc); - } - - final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { - if (providersToImplement.contains(element.getProvider())) { - if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " - + network.getPhysicalNetworkId()); - } - if (element instanceof NetworkMigrationResponder) { - ((NetworkMigrationResponder) element).commitMigration(nicSrc, network, src, src_context, dst_context); - } - } - } - // update the reservation id - final NicVO nicVo = _nicDao.findById(nicDst.getId()); - nicVo.setReservationId(nicDst.getReservationId()); - _nicDao.persist(nicVo); - } + nicMigrationService.commitNicForMigration(src, dst); } @Override public void rollbackNicForMigration(final VirtualMachineProfile src, final VirtualMachineProfile dst) { - for (final NicProfile nicDst : dst.getNics()) { - final NetworkVO network = _networksDao.findById(nicDst.getNetworkId()); - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NicProfile nicSrc = findNicProfileById(src, nicDst.getId()); - final ReservationContext src_context = new ReservationContextImpl(nicSrc.getReservationId(), null, null); - final ReservationContext dst_context = new ReservationContextImpl(nicDst.getReservationId(), null, null); - - if (guru instanceof NetworkMigrationResponder) { - ((NetworkMigrationResponder) guru).rollbackMigration(nicDst, network, dst, src_context, dst_context); - } - - if (network.getGuestType() == Network.GuestType.L2 && src.getType() == VirtualMachine.Type.User) { - _userVmMgr.setupVmForPvlan(true, dst.getVirtualMachine().getHostId(), nicDst); - } - - final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { - if (providersToImplement.contains(element.getProvider())) { - if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " - + network.getPhysicalNetworkId()); - } - if (element instanceof NetworkMigrationResponder) { - ((NetworkMigrationResponder) element).rollbackMigration(nicDst, network, dst, src_context, dst_context); - } - } - } - } + nicMigrationService.rollbackNicForMigration(src, dst); } @Override @@ -2666,48 +1897,16 @@ && isDhcpAccrossMultipleSubnetsSupported(dhcpServiceProvider)) { } public boolean isDhcpAccrossMultipleSubnetsSupported(final DhcpServiceProvider dhcpServiceProvider) { - - final Map capabilities = dhcpServiceProvider.getCapabilities().get(Network.Service.Dhcp); - final String supportsMultipleSubnets = capabilities.get(Network.Capability.DhcpAccrossMultipleSubnets); - if (supportsMultipleSubnets != null && Boolean.valueOf(supportsMultipleSubnets)) { - return true; - } - return false; + return nicDhcpCleanupService.isDhcpAccrossMultipleSubnetsSupported(dhcpServiceProvider); } private boolean isLastNicInSubnet(final NicVO nic) { - if (_nicDao.listByNetworkIdTypeAndGatewayAndBroadcastUri(nic.getNetworkId(), VirtualMachine.Type.User, nic.getIPv4Gateway(), nic.getBroadcastUri()).size() > 1) { - return false; - } - return true; + return nicDhcpCleanupService.isLastNicInSubnet(nic); } - @DB @Override public void removeDhcpServiceInSubnet(final Nic nic) { - final Network network = _networksDao.findById(nic.getNetworkId()); - final DhcpServiceProvider dhcpServiceProvider = getDhcpServiceProvider(network); - try { - final NicIpAliasVO ipAlias = _nicIpAliasDao.findByGatewayAndNetworkIdAndState(nic.getIPv4Gateway(), network.getId(), NicIpAlias.State.active); - if (ipAlias != null) { - ipAlias.setState(NicIpAlias.State.revoked); - Transaction.execute(new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(final TransactionStatus status) { - _nicIpAliasDao.update(ipAlias.getId(), ipAlias); - final IPAddressVO aliasIpaddressVo = _publicIpAddressDao.findByIpAndSourceNetworkId(ipAlias.getNetworkId(), ipAlias.getIp4Address()); - _publicIpAddressDao.unassignIpAddress(aliasIpaddressVo.getId()); - } - }); - if (!dhcpServiceProvider.removeDhcpSupportForSubnet(network)) { - logger.warn("Failed to remove the IP alias on the router, marking it as removed in db and freed the allocated IP {}", ipAlias.getIp4Address()); - } - } - } catch (final ResourceUnavailableException e) { - //failed to remove the dhcpconfig on the router. - logger.info("Unable to delete the IP alias due to unable to contact the virtualrouter."); - } - + nicDhcpCleanupService.removeDhcpServiceInSubnet(nic); } @Override @@ -2717,7 +1916,6 @@ public void removeNics(final VirtualMachineProfile vm) { _nicDao.remove(nic.getId()); } } - @Override @DB public Network createPrivateNetwork(final long networkOfferingId, final String name, final String displayText, final String gateway, final String cidr, final String vlanId, final boolean bypassVlanOverlapCheck, final Account owner, final PhysicalNetwork pNtwk, final Long vpcId) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { @@ -2766,398 +1964,64 @@ private Network createGuestNetwork(final long networkOfferingId, final String na final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { - final NetworkOfferingVO ntwkOff = _networkOfferingDao.findById(networkOfferingId); - final DataCenterVO zone = _dcDao.findById(zoneId); - // this method supports only guest network creation - if (ntwkOff.getTrafficType() != TrafficType.Guest) { - logger.warn("Only guest networks can be created using this method"); - return null; - } - - // Validate network offering - if (ntwkOff.getState() != NetworkOffering.State.Enabled) { - // see NetworkOfferingVO - final InvalidParameterValueException ex = new InvalidParameterValueException("Can't use specified network offering id as its state is not " + NetworkOffering.State.Enabled); - ex.addProxyObject(ntwkOff.getUuid(), "networkOfferingId"); - throw ex; - } - - // Validate physical network - if (pNtwk.getState() != PhysicalNetwork.State.Enabled) { - // see PhysicalNetworkVO.java - final InvalidParameterValueException ex = new InvalidParameterValueException("Specified physical network id is" + " in incorrect state:" + pNtwk.getState()); - ex.addProxyObject(pNtwk.getUuid(), "physicalNetworkId"); - throw ex; - } - - boolean ipv6 = false; - try (CheckedReservation networkReservation = new CheckedReservation(owner, domainId, Resource.ResourceType.network, null, null, 1L, reservationDao, _resourceLimitMgr)) { - if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) { - ipv6 = true; - } - // Validate zone - if (zone.getNetworkType() == NetworkType.Basic) { - // In Basic zone the network should have aclType=Domain, domainId=1, subdomainAccess=true - if (aclType == null || aclType != ACLType.Domain) { - throw new InvalidParameterValueException("Only AclType=Domain can be specified for network creation in Basic zone"); - } - - // Only one guest network is supported in Basic zone - final List guestNetworks = _networksDao.listByZoneAndTrafficType(zone.getId(), TrafficType.Guest); - if (!guestNetworks.isEmpty()) { - throw new InvalidParameterValueException("Can't have more than one Guest network in zone with network type " + NetworkType.Basic); - } - - // if zone is basic, only Shared network offerings w/o source nat service are allowed - if (!(ntwkOff.getGuestType() == GuestType.Shared && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat))) { - throw new InvalidParameterValueException("For zone of type " + NetworkType.Basic + " only offerings of " + "guestType " + GuestType.Shared + " with disabled " - + Service.SourceNat.getName() + " service are allowed"); - } - - if (domainId == null || domainId != Domain.ROOT_DOMAIN) { - throw new InvalidParameterValueException("Guest network in Basic zone should be dedicated to ROOT domain"); - } - - if (subdomainAccess == null) { - subdomainAccess = true; - } else if (!subdomainAccess) { - throw new InvalidParameterValueException("Subdomain access should be set to true for the" + " guest network in the Basic zone"); - } - - if (vlanId == null) { - vlanId = Vlan.UNTAGGED; - } else { - if (!vlanId.equalsIgnoreCase(Vlan.UNTAGGED)) { - throw new InvalidParameterValueException("Only vlan " + Vlan.UNTAGGED + " can be created in " + "the zone of type " + NetworkType.Basic); - } - } - - } else if (zone.getNetworkType() == NetworkType.Advanced) { - if (zone.isSecurityGroupEnabled()) { - if (isolatedPvlan != null) { - throw new InvalidParameterValueException("Isolated Private VLAN is not supported with security group!"); - } - // Only Account specific Isolated network with sourceNat service disabled are allowed in security group - // enabled zone - if ((ntwkOff.getGuestType() != GuestType.Shared) && (ntwkOff.getGuestType() != GuestType.L2)) { - throw new InvalidParameterValueException("Only shared or L2 guest network can be created in security group enabled zone"); - } - if (_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat)) { - throw new InvalidParameterValueException("Service SourceNat is not allowed in security group enabled zone"); - } - } - - //don't allow eip/elb networks in Advance zone - if (ntwkOff.isElasticIp() || ntwkOff.isElasticLb()) { - throw new InvalidParameterValueException("Elastic IP and Elastic LB services are supported in zone of type " + NetworkType.Basic); - } - } - - if (ipv6 && !GuestType.Shared.equals(ntwkOff.getGuestType())) { - _networkModel.checkIp6CidrSizeEqualTo64(ip6Cidr); - } - - //TODO(VXLAN): Support VNI specified - // VlanId can be specified only when network offering supports it - final boolean vlanSpecified = vlanId != null; - if (vlanSpecified != ntwkOff.isSpecifyVlan()) { - if (vlanSpecified) { - if (!isSharedNetworkWithoutSpecifyVlan(ntwkOff) && !isPrivateGatewayWithoutSpecifyVlan(ntwkOff)) { - throw new InvalidParameterValueException("Can't specify vlan; corresponding offering says specifyVlan=false"); - } - } else { - throw new InvalidParameterValueException("Vlan has to be specified; corresponding offering says specifyVlan=true"); - } + final GuestNetworkCreationPreparation preparation = guestNetworkCreationPreparationService.prepareGuestNetworkCreation(networkOfferingId, gateway, cidr, vlanId, + bypassVlanOverlapCheck, networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, ip6Gateway, ip6Cidr, isolatedPvlan, isolatedPvlanType, + externalId, isPrivateNetwork, routerIp, routerIpv6, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); + if (preparation == null) { + logger.warn("Only guest networks can be created using this method"); + return null; } - - if (vlanSpecified) { - URI uri = encodeVlanIdIntoBroadcastUri(vlanId, pNtwk); - // Aux: generate secondary URI for secondary VLAN ID (if provided) for performing checks - URI secondaryUri = StringUtils.isNotBlank(isolatedPvlan) ? BroadcastDomainType.fromString(isolatedPvlan) : null; - if (isSharedNetworkWithoutSpecifyVlan(ntwkOff) || isPrivateGatewayWithoutSpecifyVlan(ntwkOff)) { - bypassVlanOverlapCheck = true; - } - //don't allow to specify vlan tag used by physical network for dynamic vlan allocation - if (!(bypassVlanOverlapCheck && (ntwkOff.getGuestType() == GuestType.Shared || isPrivateNetwork)) - && _dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(uri)).size() > 0) { - throw new InvalidParameterValueException("The VLAN tag to use for new guest network, " + vlanId + " is already being used for dynamic vlan allocation for the guest network in zone " - + zone.getName()); - } - if (secondaryUri != null && !(bypassVlanOverlapCheck && ntwkOff.getGuestType() == GuestType.Shared) && - _dcDao.findVnet(zoneId, pNtwk.getId(), BroadcastDomainType.getValue(secondaryUri)).size() > 0) { - throw new InvalidParameterValueException(String.format( - "The VLAN tag for isolated PVLAN %s is already being used for dynamic vlan allocation for the guest network in zone %s", - isolatedPvlan, zone)); - } - if (!UuidUtils.isUuid(vlanId)) { - // For Isolated and L2 networks, don't allow to create network with vlan that already exists in the zone - if (!hasGuestBypassVlanOverlapCheck(bypassVlanOverlapCheck, ntwkOff, isPrivateNetwork)) { - if (_networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), null).size() > 0) { - throw new InvalidParameterValueException(String.format( - "Network with vlan %s already exists or overlaps with other network vlans in zone %s", - vlanId, zone)); - } else if (secondaryUri != null && _networksDao.listByZoneAndUriAndGuestType(zoneId, secondaryUri.toString(), null).size() > 0) { - throw new InvalidParameterValueException(String.format( - "Network with vlan %s already exists or overlaps with other network vlans in zone %s", - isolatedPvlan, zone)); - } else { - final List dcVnets = _datacenterVnetDao.findVnet(zoneId, BroadcastDomainType.getValue(uri)); - //for the network that is created as part of private gateway, - //the vnet is not coming from the data center vnet table, so the list can be empty - if (!dcVnets.isEmpty()) { - final DataCenterVnetVO dcVnet = dcVnets.get(0); - // Fail network creation if specified vlan is dedicated to a different account - if (dcVnet.getAccountGuestVlanMapId() != null) { - final Long accountGuestVlanMapId = dcVnet.getAccountGuestVlanMapId(); - final AccountGuestVlanMapVO map = _accountGuestVlanMapDao.findById(accountGuestVlanMapId); - if (map.getAccountId() != owner.getAccountId()) { - throw new InvalidParameterValueException("Vlan " + vlanId + " is dedicated to a different account"); - } - // Fail network creation if owner has a dedicated range of vlans but the specified vlan belongs to the system pool - } else { - final List maps = _accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(owner.getAccountId()); - if (maps != null && !maps.isEmpty()) { - final int vnetsAllocatedToAccount = _datacenterVnetDao.countVnetsAllocatedToAccount(zoneId, owner.getAccountId()); - final int vnetsDedicatedToAccount = _datacenterVnetDao.countVnetsDedicatedToAccount(zoneId, owner.getAccountId()); - if (vnetsAllocatedToAccount < vnetsDedicatedToAccount) { - throw new InvalidParameterValueException("Specified vlan " + vlanId + " doesn't belong" + " to the vlan range dedicated to the owner " - + owner.getAccountName()); - } - } + final NetworkOfferingVO ntwkOff = preparation.getNetworkOffering(); + final DataCenterVO zone = preparation.getZone(); + final Boolean subdomainAccessFinal = preparation.getSubdomainAccess(); + final Network network = Transaction.execute(new TransactionCallback<>() { + @Override + public Network doInTransaction(final TransactionStatus status) { + final DataCenterDeployment plan = preparation.getPlan(); + final NetworkVO userNetwork = preparation.getPredefinedNetwork(); + final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, + isDisplayNetworkEnabled); + Network network; + if (networks == null || networks.isEmpty()) { + throw new CloudRuntimeException("Fail to create a network"); + } else { + if (networks.size() > 0 && networks.get(0).getGuestType() == Network.GuestType.Isolated && networks.get(0).getTrafficType() == TrafficType.Guest) { + Network defaultGuestNetwork = networks.get(0); + for (final Network nw : networks) { + if (nw.getCidr() != null && nw.getCidr().equals(zone.getGuestNetworkCidr())) { + defaultGuestNetwork = nw; } } - } - } else { - // don't allow to creating shared network with given Vlan ID, if there already exists a isolated network or - // shared network with same Vlan ID in the zone - if (!bypassVlanOverlapCheck && _networksDao.listByZoneAndUriAndGuestType(zoneId, uri.toString(), GuestType.Isolated).size() > 0) { - throw new InvalidParameterValueException(String.format( - "There is an existing isolated/shared network that overlaps with vlan id:%s in zone %s", vlanId, zone)); - } - } - } - - } - - // If networkDomain is not specified, take it from the global configuration - if (_networkModel.areServicesSupportedByNetworkOffering(networkOfferingId, Service.Dns)) { - final Map dnsCapabilities = _networkModel.getNetworkOfferingServiceCapabilities(_entityMgr.findById(NetworkOffering.class, networkOfferingId), - Service.Dns); - final String isUpdateDnsSupported = dnsCapabilities.get(Capability.AllowDnsSuffixModification); - if (isUpdateDnsSupported == null || !Boolean.valueOf(isUpdateDnsSupported)) { - if (networkDomain != null) { - // TBD: NetworkOfferingId and zoneId. Send uuids instead. - throw new InvalidParameterValueException(String.format( - "Domain name change is not supported by network offering id=%d in zone %s", - networkOfferingId, zone)); - } - } else { - if (networkDomain == null) { - // 1) Get networkDomain from the corresponding account/domain/zone - if (aclType == ACLType.Domain) { - networkDomain = _networkModel.getDomainNetworkDomain(domainId, zoneId); - } else if (aclType == ACLType.Account) { - networkDomain = _networkModel.getAccountNetworkDomain(owner.getId(), zoneId); - } - - // 2) If null, generate networkDomain using domain suffix from the global config variables - if (networkDomain == null) { - networkDomain = "cs" + Long.toHexString(owner.getId()) + GuestDomainSuffix.valueIn(zoneId); - } - - } else { - // validate network domain - if (!NetUtils.verifyDomainName(networkDomain)) { - throw new InvalidParameterValueException("Invalid network domain. Total length shouldn't exceed 190 chars. Each domain " - + "label must be between 1 and 63 characters long, can contain ASCII letters 'a' through 'z', the digits '0' through '9', " - + "and the hyphen ('-'); can't start or end with \"-\""); - } - } - } - } - - // In Advance zone Cidr for Shared networks and Isolated networks w/o source nat service can't be NULL - 2.2.x - // limitation, remove after we introduce support for multiple ip ranges - // with different Cidrs for the same Shared network - final boolean cidrRequired = zone.getNetworkType() == NetworkType.Advanced - && ntwkOff.getTrafficType() == TrafficType.Guest - && (ntwkOff.getGuestType() == GuestType.Shared || (ntwkOff.getGuestType() == GuestType.Isolated - && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.SourceNat) - && !_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.Gateway))); - if (cidr == null && ip6Cidr == null && cidrRequired) { - if (ntwkOff.getGuestType() == GuestType.Shared) { - throw new InvalidParameterValueException(String.format("Gateway/netmask are required when creating %s networks.", Network.GuestType.Shared)); - } else { - throw new InvalidParameterValueException("gateway/netmask are required when create network of" + " type " + GuestType.Isolated + " with service " + Service.SourceNat.getName() + " disabled"); - } - } - - checkL2OfferingServices(ntwkOff); - - // No cidr can be specified in Basic zone - if (zone.getNetworkType() == NetworkType.Basic && cidr != null) { - throw new InvalidParameterValueException("StartIp/endIp/gateway/netmask can't be specified for zone of type " + NetworkType.Basic); - } - - // Check if cidr is RFC1918 compliant if the network is Guest Isolated for IPv4 - if (cidr != null && (ntwkOff.getGuestType() == Network.GuestType.Isolated && ntwkOff.getTrafficType() == TrafficType.Guest) && - !NetUtils.validateGuestCidr(cidr, !ConfigurationManager.AllowNonRFC1918CompliantIPs.value())) { - throw new InvalidParameterValueException("Virtual Guest Cidr " + cidr + " is not RFC 1918 or 6598 compliant"); - } - - final String networkDomainFinal = networkDomain; - final String vlanIdFinal = vlanId; - final Boolean subdomainAccessFinal = subdomainAccess; - final Network network = Transaction.execute(new TransactionCallback<>() { - @Override - public Network doInTransaction(final TransactionStatus status) { - Long physicalNetworkId = null; - if (pNtwk != null) { - physicalNetworkId = pNtwk.getId(); - } - final DataCenterDeployment plan = new DataCenterDeployment(zoneId, null, null, null, null, physicalNetworkId); - final NetworkVO userNetwork = new NetworkVO(); - userNetwork.setNetworkDomain(networkDomainFinal); - - if (cidr != null && gateway != null) { - userNetwork.setCidr(cidr); - userNetwork.setGateway(gateway); - } - - if (StringUtils.isNoneBlank(ip6Gateway, ip6Cidr)) { - userNetwork.setIp6Cidr(ip6Cidr); - userNetwork.setIp6Gateway(ip6Gateway); - } - - if (externalId != null) { - userNetwork.setExternalId(externalId); - } - - if (StringUtils.isNotBlank(routerIp)) { - userNetwork.setRouterIp(routerIp); - } - - if (StringUtils.isNotBlank(routerIpv6)) { - userNetwork.setRouterIpv6(routerIpv6); - } - - if (vrIfaceMTUs != null) { - if (vrIfaceMTUs.first() != null && vrIfaceMTUs.first() > 0) { - userNetwork.setPublicMtu(vrIfaceMTUs.first()); - } else { - userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue())); - } - - if (vrIfaceMTUs.second() != null && vrIfaceMTUs.second() > 0) { - userNetwork.setPrivateMtu(vrIfaceMTUs.second()); + network = defaultGuestNetwork; } else { - userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue())); + // For shared network + network = networks.get(0); } - } else { - userNetwork.setPublicMtu(Integer.valueOf(NetworkService.VRPublicInterfaceMtu.defaultValue())); - userNetwork.setPrivateMtu(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue())); } - if (!GuestType.L2.equals(userNetwork.getGuestType())) { - if (StringUtils.isNotBlank(ip4Dns1)) { - userNetwork.setDns1(ip4Dns1); - } - if (StringUtils.isNotBlank(ip4Dns2)) { - userNetwork.setDns2(ip4Dns2); - } - if (StringUtils.isNotBlank(ip6Dns1)) { - userNetwork.setIp6Dns1(ip6Dns1); - } - if (StringUtils.isNotBlank(ip6Dns2)) { - userNetwork.setIp6Dns2(ip6Dns2); - } + if (isResourceCountUpdateNeeded(ntwkOff)) { + changeAccountResourceCountOrRecalculateDomainResourceCount(owner.getAccountId(), domainId, isDisplayNetworkEnabled, true); } + UsageEventUtils.publishNetworkCreation(network); - if (vlanIdFinal != null) { - if (isolatedPvlan == null) { - URI uri = null; - if (UuidUtils.isUuid(vlanIdFinal)) { - //Logical router's UUID provided as VLAN_ID - userNetwork.setVlanIdAsUUID(vlanIdFinal); //Set transient field - } else { - uri = encodeVlanIdIntoBroadcastUri(vlanIdFinal, pNtwk); - } - - if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString()).size() > 0) { - throw new InvalidParameterValueException(String.format( - "Network with vlan %s already exists or overlaps with other network pvlans in zone %s", - vlanIdFinal, zone)); - } - - userNetwork.setBroadcastUri(uri); - if (!vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { - userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan); - } else { - userNetwork.setBroadcastDomainType(BroadcastDomainType.Native); - } - } else { - if (vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { - throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!"); - } - URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan, isolatedPvlanType.toString()); - if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) { - throw new InvalidParameterValueException(String.format( - "Network with primary vlan %s and secondary vlan %s type %s already exists or overlaps with other network pvlans in zone %s", - vlanIdFinal, isolatedPvlan, isolatedPvlanType, zone)); - } - userNetwork.setBroadcastUri(uri); - userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan); - userNetwork.setPvlanType(isolatedPvlanType); - } + return network; } - userNetwork.setNetworkCidrSize(networkCidrSize); - userNetwork.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); - final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, - isDisplayNetworkEnabled); - Network network; - if (networks == null || networks.isEmpty()) { - throw new CloudRuntimeException("Fail to create a network"); - } else { - if (networks.size() > 0 && networks.get(0).getGuestType() == Network.GuestType.Isolated && networks.get(0).getTrafficType() == TrafficType.Guest) { - Network defaultGuestNetwork = networks.get(0); - for (final Network nw : networks) { - if (nw.getCidr() != null && nw.getCidr().equals(zone.getGuestNetworkCidr())) { - defaultGuestNetwork = nw; - } - } - network = defaultGuestNetwork; - } else { - // For shared network - network = networks.get(0); - } - } - - if (isResourceCountUpdateNeeded(ntwkOff)) { - changeAccountResourceCountOrRecalculateDomainResourceCount(owner.getAccountId(), domainId, isDisplayNetworkEnabled, true); - } - UsageEventUtils.publishNetworkCreation(network); - - return network; - } - }); + }); - CallContext.current().setEventDetails("Network ID: " + network.getUuid()); - CallContext.current().putContextParameter(Network.class, network.getUuid()); - return network; + CallContext.current().setEventDetails("Network ID: " + network.getUuid()); + CallContext.current().putContextParameter(Network.class, network.getUuid()); + return network; } } @Override public boolean isSharedNetworkWithoutSpecifyVlan(NetworkOffering offering) { - if (offering == null || offering.getTrafficType() != TrafficType.Guest || offering.getGuestType() != GuestType.Shared) { - return false; - } - return !offering.isSpecifyVlan(); + return networkOfferingVlanValidationService.isSharedNetworkWithoutSpecifyVlan(offering); } private boolean isPrivateGatewayWithoutSpecifyVlan(NetworkOffering ntwkOff) { - return ntwkOff.getId() == _networkOfferingDao.findByUniqueName(NetworkOffering.SystemPrivateGatewayNetworkOfferingWithoutVlan).getId(); + return networkOfferingVlanValidationService.isPrivateGatewayWithoutSpecifyVlan(ntwkOff); } /** @@ -3166,28 +2030,7 @@ private boolean isPrivateGatewayWithoutSpecifyVlan(NetworkOffering ntwkOff) { * @return Broadcast URI, e.g. 'vlan://vlan_ID' or 'vxlan://vlxan_ID' */ protected URI encodeVlanIdIntoBroadcastUri(String vlanId, PhysicalNetwork pNtwk) { - if (pNtwk == null) { - throw new InvalidParameterValueException(String.format("Failed to encode VLAN/VXLAN %s into a Broadcast URI. Physical Network cannot be null.", vlanId)); - } - - if (!pNtwk.getIsolationMethods().isEmpty() && StringUtils.isNotBlank(pNtwk.getIsolationMethods().get(0))) { - String isolationMethod = pNtwk.getIsolationMethods().get(0).toLowerCase(); - String vxlan = BroadcastDomainType.Vxlan.toString().toLowerCase(); - if (isolationMethod.equals(vxlan)) { - return BroadcastDomainType.encodeStringIntoBroadcastUri(vlanId, BroadcastDomainType.Vxlan); - } - } - return BroadcastDomainType.fromString(vlanId); - } - - /** - * Checks bypass VLAN id/range overlap check during network creation for guest networks - * - * @param bypassVlanOverlapCheck bypass VLAN id/range overlap check - * @param ntwkOff network offering - */ - private boolean hasGuestBypassVlanOverlapCheck(final boolean bypassVlanOverlapCheck, final NetworkOfferingVO ntwkOff, final boolean isPrivateNetwork) { - return bypassVlanOverlapCheck && (ntwkOff.getGuestType() != GuestType.Isolated || isPrivateNetwork); + return networkOfferingVlanValidationService.encodeVlanIdIntoBroadcastUri(vlanId, pNtwk); } /** @@ -3198,12 +2041,7 @@ private boolean hasGuestBypassVlanOverlapCheck(final boolean bypassVlanOverlapCh * @param ntwkOff network offering */ protected void checkL2OfferingServices(NetworkOfferingVO ntwkOff) { - if (ntwkOff.getGuestType() == GuestType.L2 && !_networkModel.listNetworkOfferingServices(ntwkOff.getId()).isEmpty() && - (!_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.UserData) || - (_networkModel.areServicesSupportedByNetworkOffering(ntwkOff.getId(), Service.UserData) && - _networkModel.listNetworkOfferingServices(ntwkOff.getId()).size() > 1))) { - throw new InvalidParameterValueException("For L2 networks, only UserData service is allowed"); - } + networkOfferingVlanValidationService.checkL2OfferingServices(ntwkOff); } @Override @@ -3553,51 +2391,15 @@ private void changeAccountResourceCountOrRecalculateDomainResourceCount(Long acc } private void publishDeletedVlanRanges(List deletedVlanRangeToPublish) { - if (CollectionUtils.isNotEmpty(deletedVlanRangeToPublish)) { - for (VlanVO vlan : deletedVlanRangeToPublish) { - _messageBus.publish(_name, MESSAGE_DELETE_VLAN_IP_RANGE_EVENT, PublishScope.LOCAL, vlan); - } - } + networkVlanRangeCleanupService.publishDeletedVlanRanges(_name, deletedVlanRangeToPublish); } @Override public boolean isResourceCountUpdateNeeded(NetworkOffering networkOffering) { return !networkOffering.isSystemOnly(); } - protected Pair> deleteVlansInNetwork(final NetworkVO network, final long userId, final Account callerAccount) { - final long networkId = network.getId(); - //cleanup Public vlans - final List publicVlans = _vlanDao.listVlansByNetworkId(networkId); - List deletedPublicVlanRange = new ArrayList<>(); - boolean result = true; - for (final VlanVO vlan : publicVlans) { - VlanVO vlanRange = _configMgr.deleteVlanAndPublicIpRange(userId, vlan.getId(), callerAccount); - if (vlanRange == null) { - logger.warn("Failed to delete vlan [id: {}, uuid: {}];", vlan.getId(), vlan.getUuid()); - result = false; - } else { - deletedPublicVlanRange.add(vlanRange); - } - } - - //cleanup private vlans - final int privateIpAllocCount = _privateIpDao.countAllocatedByNetworkId(networkId); - if (privateIpAllocCount > 0) { - logger.warn("Can't delete Private IP range for Network {} as it has allocated IP addresses", network); - result = false; - } else { - _privateIpDao.deleteByNetworkId(networkId); - logger.debug("Deleted ip range for private network {}", network); - } - - // release vlans of user-shared networks without specifyvlan - if (isSharedNetworkWithoutSpecifyVlan(_networkOfferingDao.findById(network.getNetworkOfferingId()))) { - logger.debug("Releasing vnet for the network {}", network); - _dcDao.releaseVnet(BroadcastDomainType.getValue(network.getBroadcastUri()), network.getDataCenterId(), - network.getPhysicalNetworkId(), network.getAccountId(), network.getReservationId()); - } - return new Pair<>(result, deletedPublicVlanRange); + return networkVlanRangeCleanupService.deleteVlansInNetwork(network, userId, callerAccount); } public class NetworkGarbageCollector extends ManagedContextRunnable { @@ -3808,27 +2610,7 @@ public boolean areRoutersRunning(final List routers) { */ @Override public void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile) { - - final List networkProviders = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { - if (networkProviders.contains(element.getProvider())) { - if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " - + network.getPhysicalNetworkId()); - } - if (vmProfile.getType() == Type.User && element.getProvider() != null) { - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dhcp) - && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dhcp, element.getProvider()) && element instanceof DhcpServiceProvider) { - final DhcpServiceProvider sp = (DhcpServiceProvider) element; - try { - sp.removeDhcpEntry(network, nicProfile, vmProfile); - } catch (ResourceUnavailableException e) { - logger.error("Failed to remove dhcp-dns entry due to: ", e); - } - } - } - } - } + nicDhcpCleanupService.cleanupNicDhcpDnsEntry(network, vmProfile, nicProfile); } /** @@ -3916,55 +2698,22 @@ public NetworkProfile convertNetworkToNetworkProfile(final long networkId) { @Override public UserDataServiceProvider getPasswordResetProvider(final Network network) { - final String passwordProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.UserData); - - if (passwordProvider == null) { - logger.debug("Network {} doesn't support service {}", network, Service.UserData.getName()); - return null; - } - - return (UserDataServiceProvider) _networkModel.getElementImplementingProvider(passwordProvider); + return networkProviderResolutionService.getPasswordResetProvider(network); } @Override public UserDataServiceProvider getSSHKeyResetProvider(final Network network) { - final String SSHKeyProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.UserData); - - if (SSHKeyProvider == null) { - logger.debug("Network {} doesn't support service", network, Service.UserData.getName()); - return null; - } - - return (UserDataServiceProvider) _networkModel.getElementImplementingProvider(SSHKeyProvider); + return networkProviderResolutionService.getSSHKeyResetProvider(network); } @Override public DhcpServiceProvider getDhcpServiceProvider(final Network network) { - final String DhcpProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.Dhcp); - - if (DhcpProvider == null) { - logger.debug("Network {} doesn't support service {}", network, Service.Dhcp.getName()); - return null; - } - - final NetworkElement element = _networkModel.getElementImplementingProvider(DhcpProvider); - if (element instanceof DhcpServiceProvider) { - return (DhcpServiceProvider) element; - } else { - return null; - } + return networkProviderResolutionService.getDhcpServiceProvider(network); } @Override public DnsServiceProvider getDnsServiceProvider(final Network network) { - final String dnsProvider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.Dns); - - if (dnsProvider == null) { - logger.debug("Network {} doesn't support service {}", network, Service.Dhcp.getName()); - return null; - } - - return (DnsServiceProvider) _networkModel.getElementImplementingProvider(dnsProvider); + return networkProviderResolutionService.getDnsServiceProvider(network); } protected boolean isSharedNetworkWithServices(final Network network) { @@ -3992,39 +2741,7 @@ protected boolean isSharedNetworkOfferingWithServices(final long networkOffering @Override public List listVmNics(final long vmId, final Long nicId, final Long networkId, String keyword) { - List result; - - if (keyword == null || keyword.isEmpty()) { - if (nicId == null && networkId == null) { - result = _nicDao.listByVmId(vmId); - } else { - result = _nicDao.listByVmIdAndNicIdAndNtwkId(vmId, nicId, networkId); - } - } else { - result = _nicDao.listByVmIdAndKeyword(vmId, keyword); - } - - for (final NicVO nic : result) { - if (_networkModel.isProviderForNetwork(Provider.NiciraNvp, nic.getNetworkId())) { - //For NSX Based networks, add nsxlogicalswitch, nsxlogicalswitchport to each result - logger.info("Listing NSX logical switch and logical switch por for each nic"); - final NetworkVO network = _networksDao.findById(nic.getNetworkId()); - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NetworkGuruAdditionalFunctions guruFunctions = (NetworkGuruAdditionalFunctions) guru; - - final Map nsxParams = guruFunctions.listAdditionalNicParams(nic.getUuid()); - if (nsxParams != null) { - final String lswitchUuuid = nsxParams.containsKey(NetworkGuruAdditionalFunctions.NSX_LSWITCH_UUID) - ? (String) nsxParams.get(NetworkGuruAdditionalFunctions.NSX_LSWITCH_UUID) : null; - final String lswitchPortUuuid = nsxParams.containsKey(NetworkGuruAdditionalFunctions.NSX_LSWITCHPORT_UUID) - ? (String) nsxParams.get(NetworkGuruAdditionalFunctions.NSX_LSWITCHPORT_UUID) : null; - nic.setNsxLogicalSwitchUuid(lswitchUuuid); - nic.setNsxLogicalSwitchPortUuid(lswitchPortUuuid); - } - } - } - - return result; + return nicAuxiliaryService.listVmNics(vmId, nicId, networkId, keyword, networkGurus); } @DB @@ -4050,267 +2767,11 @@ public void doInTransactionWithoutResult(final TransactionStatus status) throws } private boolean cleanupNetworkResources(final long networkId, final Account caller, final long callerUserId) { - boolean success = true; - final NetworkVO network = _networksDao.findById(networkId); - final NetworkOfferingVO networkOffering= _networkOfferingDao.findById(network.getNetworkOfferingId()); - - //remove BGP peers from the network - if (routedIpv4Manager.removeBgpPeersFromNetwork(network) != null) { - logger.debug("Successfully removed BGP peers from network id={}", networkId); - } else { - success = false; - logger.warn("Failed to remove BGP peers from network as a part of network id={} cleanup", networkId); - } - - //remove all PF/Static Nat rules for the network - try { - if (_rulesMgr.revokeAllPFStaticNatRulesForNetwork(networkId, callerUserId, caller)) { - logger.debug("Successfully cleaned up portForwarding/staticNat rules for network {}", network); - } else { - success = false; - logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup", network); - } - } catch (final ResourceUnavailableException ex) { - success = false; - // shouldn't even come here as network is being cleaned up after all network elements are shutdown - logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup due to resourceUnavailable", network, ex); - } - - //remove all LB rules for the network - if (_lbMgr.removeAllLoadBalanacersForNetwork(networkId, caller, callerUserId)) { - logger.debug("Successfully cleaned up load balancing rules for network {}", network); - } else { - // shouldn't even come here as network is being cleaned up after all network elements are shutdown - success = false; - logger.warn("Failed to cleanup LB rules as a part of network {} cleanup", network); - } - - //revoke all firewall rules for the network - try { - if (_firewallMgr.revokeAllFirewallRulesForNetwork(network, callerUserId, caller)) { - logger.debug("Successfully cleaned up firewallRules rules for network {}", network); - } else { - success = false; - logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup", network); - } - } catch (final ResourceUnavailableException ex) { - success = false; - // shouldn't even come here as network is being cleaned up after all network elements are shutdown - logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup due to resourceUnavailable", network, ex); - } - - //revoke all network ACLs for network - try { - if (_networkACLMgr.revokeACLItemsForNetwork(networkId)) { - logger.debug("Successfully cleaned up NetworkACLs for network {}", network); - } else { - success = false; - logger.warn("Failed to cleanup NetworkACLs as a part of network {} cleanup", network); - } - } catch (final ResourceUnavailableException ex) { - success = false; - logger.warn("Failed to cleanup Network ACLs as a part of network {} cleanup due to resourceUnavailable ", network, ex); - } - - //release all ip addresses - final List ipsToRelease = _ipAddressDao.listByAssociatedNetwork(networkId, null); - for (final IPAddressVO ipToRelease : ipsToRelease) { - if (ipToRelease.getVpcId() == null) { - if (!ipToRelease.isPortable()) { - final IPAddressVO ip = _ipAddrMgr.markIpAsUnavailable(ipToRelease.getId()); - assert ip != null : "Unable to mark the ip address id=" + ipToRelease.getId() + " as unavailable."; - } else { - // portable IP address are associated with owner, until explicitly requested to be disassociated - // so as part of network clean up just break IP association with guest network - ipToRelease.setAssociatedWithNetworkId(null); - _ipAddressDao.update(ipToRelease.getId(), ipToRelease); - logger.debug("Portable IP address {} is no longer associated with any network", ipToRelease); - } - } else { - _vpcMgr.unassignIPFromVpcNetwork(ipToRelease, network); - } - } - - try { - if (!_ipAddrMgr.applyIpAssociations(network, true)) { - logger.warn("Unable to apply ip address associations for {}", network); - success = false; - } - } catch (final ResourceUnavailableException e) { - throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e); - } - - annotationDao.removeByEntityType(AnnotationService.EntityType.NETWORK.name(), network.getUuid()); - - return success; + return networkResourceCleanupService.cleanupNetworkResources(networkId, caller, callerUserId); } private boolean shutdownNetworkResources(final Network network, final Account caller, final long callerUserId) { - // This method cleans up network rules on the backend w/o touching them in the DB - boolean success = true; - - // Mark all PF rules as revoked and apply them on the backend (not in the DB) - final List pfRules = _portForwardingRulesDao.listByNetwork(network.getId()); - logger.debug("Releasing {} port forwarding rules for network id={} as a part of shutdownNetworkRules.", pfRules.size(), network); - - for (final PortForwardingRuleVO pfRule : pfRules) { - logger.trace("Marking pf rule {} with Revoke state", pfRule); - pfRule.setState(FirewallRule.State.Revoke); - } - - try { - if (!_firewallMgr.applyRules(pfRules, true, false)) { - logger.warn("Failed to cleanup pf rules as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup pf rules as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - // Mark all static rules as revoked and apply them on the backend (not in the DB) - final List firewallStaticNatRules = _firewallDao.listByNetworkAndPurpose(network.getId(), Purpose.StaticNat); - final List staticNatRules = new ArrayList<>(); - logger.debug("Releasing {} static nat rules for network {} as a part of shutdownNetworkRules", firewallStaticNatRules.size(), network); - - for (final FirewallRuleVO firewallStaticNatRule : firewallStaticNatRules) { - logger.trace("Marking static nat rule {} with Revoke state", firewallStaticNatRule); - final IpAddress ip = _ipAddressDao.findById(firewallStaticNatRule.getSourceIpAddressId()); - final FirewallRuleVO ruleVO = _firewallDao.findById(firewallStaticNatRule.getId()); - - if (ip == null || !ip.isOneToOneNat() || ip.getAssociatedWithVmId() == null) { - throw new InvalidParameterValueException(String.format("Source ip address of the rule %s is not static nat enabled", firewallStaticNatRule)); - } - - //String dstIp = _networkModel.getIpInNetwork(ip.getAssociatedWithVmId(), firewallStaticNatRule.getNetworkId()); - ruleVO.setState(FirewallRule.State.Revoke); - staticNatRules.add(new StaticNatRuleImpl(ruleVO, ip.getVmIp())); - } - - try { - if (!_firewallMgr.applyRules(staticNatRules, true, false)) { - logger.warn("Failed to cleanup static nat rules as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup static nat rules as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - try { - if (!_lbMgr.revokeLoadBalancersForNetwork(network, Scheme.Public)) { - logger.warn("Failed to cleanup public lb rules as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup public lb rules as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - try { - if (!_lbMgr.revokeLoadBalancersForNetwork(network, Scheme.Internal)) { - logger.warn("Failed to cleanup internal lb rules as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup public lb rules as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - // revoke all firewall rules for the network w/o applying them on the DB - final List firewallRules = _firewallDao.listByNetworkPurposeTrafficType(network.getId(), Purpose.Firewall, FirewallRule.TrafficType.Ingress); - logger.debug("Releasing firewall ingress rules for network {} as a part of shutdownNetworkRules", firewallRules.size(), network); - - for (final FirewallRuleVO firewallRule : firewallRules) { - logger.trace("Marking firewall ingress rule {} with Revoke state", firewallRule); - firewallRule.setState(FirewallRule.State.Revoke); - } - - try { - if (!_firewallMgr.applyRules(firewallRules, true, false)) { - logger.warn("Failed to cleanup firewall ingress rules as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup firewall ingress rules as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - final List firewallEgressRules = _firewallDao.listByNetworkPurposeTrafficType(network.getId(), Purpose.Firewall, FirewallRule.TrafficType.Egress); - logger.debug("Releasing {} firewall egress rules for network {} as a part of shutdownNetworkRules", firewallEgressRules.size(), network); - - try { - // delete default egress rule - final DataCenter zone = _dcDao.findById(network.getDataCenterId()); - if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Firewall) - && (network.getGuestType() == Network.GuestType.Isolated || network.getGuestType() == Network.GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) { - // add default egress rule to accept the traffic - _firewallMgr.applyDefaultEgressFirewallRule(network.getId(), _networkModel.getNetworkEgressDefaultPolicy(network.getId()), false); - } - - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup firewall default egress rule as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - for (final FirewallRuleVO firewallRule : firewallEgressRules) { - logger.trace("Marking firewall egress rule {} with Revoke state", firewallRule); - firewallRule.setState(FirewallRule.State.Revoke); - } - - try { - if (!_firewallMgr.applyRules(firewallEgressRules, true, false)) { - logger.warn("Failed to cleanup firewall egress rules as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup firewall egress rules as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - if (network.getVpcId() != null) { - logger.debug("Releasing Network ACL Items for network {} as a part of shutdownNetworkRules", network); - - try { - //revoke all Network ACLs for the network w/o applying them in the DB - if (!_networkACLMgr.revokeACLItemsForNetwork(network.getId())) { - logger.warn("Failed to cleanup network ACLs as a part of shutdownNetworkRules"); - success = false; - } - } catch (final ResourceUnavailableException ex) { - logger.warn("Failed to cleanup network ACLs as a part of shutdownNetworkRules due to ", ex); - success = false; - } - - } - - //release all static nats for the network - if (!_rulesMgr.applyStaticNatForNetwork(network, false, caller, true)) { - logger.warn("Failed to disable static nats as part of shutdownNetworkRules for network {}", network); - success = false; - } - - // Get all ip addresses, mark as releasing and release them on the backend - final List userIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), null); - final List publicIpsToRelease = new ArrayList<>(); - if (userIps != null && !userIps.isEmpty()) { - for (final IPAddressVO userIp : userIps) { - userIp.setState(IpAddress.State.Releasing); - final PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, _vlanDao.findById(userIp.getVlanId())); - publicIpsToRelease.add(publicIp); - } - } - - try { - if (!_ipAddrMgr.applyIpAssociations(network, true, true, publicIpsToRelease)) { - logger.warn("Unable to apply ip address associations for {} as a part of shutdownNetworkRules", network); - success = false; - } - } catch (final ResourceUnavailableException e) { - throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e); - } - - return success; + return networkResourceCleanupService.shutdownNetworkResources(network, caller, callerUserId); } @Override @@ -4333,78 +2794,7 @@ public void processHostAdded(long hostId) { @Override public void processConnect(final Host host, final StartupCommand cmd, final boolean forRebalance) throws ConnectionException { - if (!(cmd instanceof StartupRoutingCommand) || cmd.isConnectionTransferred()) { - return; - } - final long hostId = host.getId(); - final StartupRoutingCommand startup = (StartupRoutingCommand) cmd; - - final String dataCenter = startup.getDataCenter(); - - long dcId; - DataCenterVO dc = _dcDao.findByName(dataCenter); - if (dc == null) { - try { - dcId = Long.parseLong(dataCenter); - dc = _dcDao.findById(dcId); - } catch (final NumberFormatException e) { - } - } - if (dc == null) { - throw new IllegalArgumentException("Host " + startup.getPrivateIpAddress() + " sent incorrect data center: " + dataCenter); - } - dcId = dc.getId(); - final HypervisorType hypervisorType = startup.getHypervisorType(); - - logger.debug("Host's hypervisorType is: {}", hypervisorType); - - final List networkInfoList = new ArrayList<>(); - - // list all physicalnetworks in the zone & for each get the network names - final List physicalNtwkList = _physicalNetworkDao.listByZone(dcId); - for (final PhysicalNetworkVO pNtwk : physicalNtwkList) { - final String publicName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Public, hypervisorType); - final String privateName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Management, hypervisorType); - final String guestName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Guest, hypervisorType); - final String storageName = _pNTrafficTypeDao.getNetworkTag(pNtwk.getId(), TrafficType.Storage, hypervisorType); - // String controlName = _pNTrafficTypeDao._networkModel.getNetworkTag(pNtwk.getId(), TrafficType.Control, hypervisorType); - final PhysicalNetworkSetupInfo info = new PhysicalNetworkSetupInfo(); - info.setPhysicalNetworkId(pNtwk.getId()); - info.setGuestNetworkName(guestName); - info.setPrivateNetworkName(privateName); - info.setPublicNetworkName(publicName); - info.setStorageNetworkName(storageName); - final PhysicalNetworkTrafficTypeVO mgmtTraffic = _pNTrafficTypeDao.findBy(pNtwk.getId(), TrafficType.Management); - if (mgmtTraffic != null) { - final String vlan = mgmtTraffic.getVlan(); - info.setMgmtVlan(vlan); - } - networkInfoList.add(info); - } - - // send the names to the agent - logger.debug("Sending CheckNetworkCommand to check the Network is setup correctly on Agent"); - final CheckNetworkCommand nwCmd = new CheckNetworkCommand(networkInfoList); - - final CheckNetworkAnswer answer = (CheckNetworkAnswer) _agentMgr.easySend(hostId, nwCmd); - - if (answer == null) { - logger.warn("Unable to get an answer to the CheckNetworkCommand from agent: {}", host); - throw new ConnectionException(true, String.format("Unable to get an answer to the CheckNetworkCommand from agent: %s", host)); - } - - if (!answer.getResult()) { - logger.warn("Unable to setup agent {} due to {}", host, answer.getDetails()); - final String msg = "Incorrect Network setup on agent, Reinitialize agent after network names are setup, details : " + answer.getDetails(); - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, dcId, host.getPodId(), msg, msg); - throw new ConnectionException(true, msg); - } else { - if (answer.needReconnect()) { - throw new ConnectionException(false, "Reinitialize agent after network setup."); - } - logger.debug("Network setup is correct on Agent"); - return; - } + networkHostSetupService.processConnect(host, cmd, forRebalance); } @Override @@ -4437,53 +2827,11 @@ public boolean processTimeout(final long agentId, final long seq) { @Override public Map finalizeServicesAndProvidersForNetwork(final NetworkOffering offering, final Long physicalNetworkId) { - final Map svcProviders = new HashMap<>(); - final Map> providerSvcs = new HashMap<>(); - final List servicesMap = _ntwkOfferingSrvcDao.listByNetworkOfferingId(offering.getId()); - - final boolean checkPhysicalNetwork = physicalNetworkId != null ? true : false; - - for (final NetworkOfferingServiceMapVO serviceMap : servicesMap) { - if (svcProviders.containsKey(serviceMap.getService())) { - // FIXME - right now we pick up the first provider from the list, need to add more logic based on - // provider load, etc - continue; - } - - final String service = serviceMap.getService(); - String provider = serviceMap.getProvider(); - - if (provider == null) { - provider = _networkModel.getDefaultUniqueProviderForService(service).getName(); - } - - // check that provider is supported - if (checkPhysicalNetwork) { - if (!_pNSPDao.isServiceProviderEnabled(physicalNetworkId, provider, service)) { - throw new UnsupportedServiceException("Provider " + provider + " is either not enabled or doesn't " + "support service " + service + " in physical network id=" - + physicalNetworkId); - } - } - - svcProviders.put(service, provider); - List l = providerSvcs.get(provider); - if (l == null) { - providerSvcs.put(provider, l = new ArrayList<>()); - } - l.add(service); - } - - return svcProviders; + return networkProviderMappingService.finalizeServicesAndProvidersForNetwork(offering, physicalNetworkId); } private List getNetworkProviders(final long networkId) { - final List providerNames = _ntwkSrvcDao.getDistinctProviders(networkId); - final List providers = new ArrayList<>(); - for (final String providerName : providerNames) { - providers.add(Network.Provider.getProvider(providerName)); - } - - return providers; + return networkProviderMappingService.getNetworkProviders(networkId); } @Override @@ -4496,23 +2844,7 @@ public boolean setupDns(final Network network, final Provider provider) { } protected NicProfile getNicProfileForVm(final Network network, final NicProfile requested, final VirtualMachine vm) { - NicProfile nic = null; - if (requested != null && requested.getBroadCastUri() != null) { - final String broadcastUri = requested.getBroadCastUri().toString(); - final String ipAddress = requested.getIPv4Address(); - final NicVO nicVO = _nicDao.findByNetworkIdInstanceIdAndBroadcastUri(network.getId(), vm.getId(), broadcastUri); - if (nicVO != null) { - if (ipAddress == null || nicVO.getIPv4Address().equals(ipAddress)) { - nic = _networkModel.getNicProfile(vm, network.getId(), broadcastUri); - } - } - } else { - final NicVO nicVO = _nicDao.findByNtwkIdAndInstanceId(network.getId(), vm.getId()); - if (nicVO != null) { - nic = _networkModel.getNicProfile(vm, network.getId(), null); - } - } - return nic; + return nicProfileLifecycleMappingService.getNicProfileForVm(network, requested, vm); } @Override @@ -4561,72 +2893,22 @@ public NicProfile createNicForVm(final Network network, final NicProfile request } private boolean getNicProfileDefaultNic(NicProfile nicProfile) { - if (nicProfile != null) { - logger.debug("Using requested nic profile isDefaultNic value [{}].", nicProfile.isDefaultNic()); - return nicProfile.isDefaultNic(); - } - - logger.debug("Using isDefaultNic default value [false] as requested nic profile is null."); - return false; + return nicProfileLifecycleMappingService.getNicProfileDefaultNic(nicProfile); } @Override public List getNicProfiles(final Long vmId, HypervisorType hypervisorType) { - final List nics = _nicDao.listByVmId(vmId); - final List profiles = new ArrayList<>(); - - if (nics != null) { - for (final Nic nic : nics) { - final NetworkVO network = _networksDao.findById(nic.getNetworkId()); - final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vmId); - - final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, - _networkModel.isSecurityGroupSupportedInNetwork(network), _networkModel.getNetworkTag(hypervisorType, network)); - guru.updateNicProfile(profile, network); - profiles.add(profile); - } - } - return profiles; + return nicProfileLifecycleMappingService.getNicProfiles(vmId, hypervisorType); } @Override public List getNicProfiles(final VirtualMachine vm) { - return getNicProfiles(vm.getId(), vm.getHypervisorType()); + return nicProfileLifecycleMappingService.getNicProfiles(vm); } @Override public Map getSystemVMAccessDetails(final VirtualMachine vm) { - final Map accessDetails = new HashMap<>(); - accessDetails.put(NetworkElementCommand.ROUTER_NAME, vm.getInstanceName()); - String privateIpAddress = null; - for (final NicProfile profile : getNicProfiles(vm)) { - if (profile == null) { - continue; - } - final Network network = _networksDao.findById(profile.getNetworkId()); - if (network == null) { - continue; - } - final String address = profile.getIPv4Address(); - if (network.getTrafficType() == Networks.TrafficType.Control) { - accessDetails.put(NetworkElementCommand.ROUTER_IP, address); - } - if (network.getTrafficType() == Networks.TrafficType.Guest) { - accessDetails.put(NetworkElementCommand.ROUTER_GUEST_IP, address); - } - if (network.getTrafficType() == Networks.TrafficType.Management) { - privateIpAddress = address; - } - if (network.getTrafficType() != null && StringUtils.isNotEmpty(address)) { - accessDetails.put(network.getTrafficType().name(), address); - } - } - - if (privateIpAddress != null && StringUtils.isEmpty(accessDetails.get(NetworkElementCommand.ROUTER_IP))) { - accessDetails.put(NetworkElementCommand.ROUTER_IP, privateIpAddress); - } - return accessDetails; + return nicProfileLifecycleMappingService.getSystemVMAccessDetails(vm); } @Override @@ -4637,85 +2919,23 @@ public boolean stateTransitTo(final Network network, final Network.Event e) thro private void setStateMachine() { _stateMachine = Network.State.getStateMachine(); } - - private Map> getServiceProvidersMap(final long networkId) { - final Map> map = new HashMap<>(); - final List nsms = _ntwkSrvcDao.getServicesInNetwork(networkId); - for (final NetworkServiceMapVO nsm : nsms) { - Set providers = map.get(Service.getService(nsm.getService())); - if (providers == null) { - providers = new HashSet<>(); - } - providers.add(Provider.getProvider(nsm.getProvider())); - map.put(Service.getService(nsm.getService()), providers); - } - return map; - } - @Override public List getProvidersForServiceInNetwork(final Network network, final Service service) { - final Map> service2ProviderMap = getServiceProvidersMap(network.getId()); - if (service2ProviderMap.get(service) != null) { - final List providers = new ArrayList<>(service2ProviderMap.get(service)); - return providers; - } - return null; + return networkProviderResolutionService.getProvidersForServiceInNetwork(network, service); } protected List getElementForServiceInNetwork(final Network network, final Service service) { - final List elements = new ArrayList<>(); - final List providers = getProvidersForServiceInNetwork(network, service); - //Only support one provider now - if (providers == null) { - logger.error("Cannot find {} provider for network {}", service.getName(), network); - return null; - } - if (providers.size() != 1 && service != Service.Lb) { - //support more than one LB providers only - logger.error("Found {} {} providers for network! {}", providers.size(), service.getName(), network); - return null; - } - - for (final Provider provider : providers) { - final NetworkElement element = _networkModel.getElementImplementingProvider(provider.getName()); - logger.info("Let {} handle {} in network {}", element.getName(), service.getName(), network); - elements.add(element); - } - return elements; + return networkProviderResolutionService.getElementForServiceInNetwork(network, service); } @Override public StaticNatServiceProvider getStaticNatProviderForNetwork(final Network network) { - //only one provider per Static nat service is supoprted - final NetworkElement element = getElementForServiceInNetwork(network, Service.StaticNat).get(0); - assert element instanceof StaticNatServiceProvider; - return (StaticNatServiceProvider) element; + return networkProviderResolutionService.getStaticNatProviderForNetwork(network); } @Override public LoadBalancingServiceProvider getLoadBalancingProviderForNetwork(final Network network, final Scheme lbScheme) { - final List lbElements = getElementForServiceInNetwork(network, Service.Lb); - NetworkElement lbElement = null; - if (lbElements.size() > 1) { - String providerName; - //get network offering details - final NetworkOffering off = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId()); - if (lbScheme == Scheme.Public) { - providerName = _ntwkOffDetailsDao.getDetail(off.getId(), NetworkOffering.Detail.PublicLbProvider); - } else { - providerName = _ntwkOffDetailsDao.getDetail(off.getId(), NetworkOffering.Detail.InternalLbProvider); - } - if (providerName == null) { - throw new InvalidParameterValueException("Can't find Lb provider supporting scheme " + lbScheme.toString() + " in network " + network); - } - lbElement = _networkModel.getElementImplementingProvider(providerName); - } else if (lbElements.size() == 1) { - lbElement = lbElements.get(0); - } - - assert lbElement != null; - assert lbElement instanceof LoadBalancingServiceProvider; - return (LoadBalancingServiceProvider) lbElement; + return networkProviderResolutionService.getLoadBalancingProviderForNetwork(network, lbScheme); } @Override @@ -4723,225 +2943,41 @@ public boolean isNetworkInlineMode(final Network network) { final NetworkOfferingVO offering = _networkOfferingDao.findById(network.getNetworkOfferingId()); return offering.isInline(); } - @Override public boolean isSecondaryIpSetForNic(final long nicId) { - final NicVO nic = _nicDao.findById(nicId); - return nic.getSecondaryIp(); + return nicAuxiliaryService.isSecondaryIpSetForNic(nicId); } private boolean removeVmSecondaryIpsOfNic(final long nicId) { - Transaction.execute(new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(final TransactionStatus status) { - final List ipList = _nicSecondaryIpDao.listByNicId(nicId); - if (ipList != null) { - for (final NicSecondaryIpVO ip : ipList) { - _nicSecondaryIpDao.remove(ip.getId()); - } - logger.debug("Revoving nic secondary ip entry ..."); - } - } - }); - - return true; + return nicAuxiliaryService.removeVmSecondaryIpsOfNic(nicId); } @Override public NicVO savePlaceholderNic(final Network network, final String ip4Address, final String ip6Address, final Type vmType) { - return savePlaceholderNic(network, ip4Address, ip6Address, null, null, null, vmType); + return nicAuxiliaryService.savePlaceholderNic(network, ip4Address, ip6Address, vmType); } @Override public NicVO savePlaceholderNic(final Network network, final String ip4Address, final String ip6Address, final String ip6Cidr, final String ip6Gateway, final String reserver, final Type vmType) { - final NicVO nic = new NicVO(null, null, network.getId(), null); - nic.setIPv4Address(ip4Address); - nic.setIPv6Address(ip6Address); - nic.setIPv6Cidr(ip6Cidr); - nic.setIPv6Gateway(ip6Gateway); - nic.setReservationStrategy(ReservationStrategy.PlaceHolder); - if (reserver != null) { - nic.setReserver(reserver); - } - nic.setState(Nic.State.Reserved); - nic.setVmType(vmType); - return _nicDao.persist(nic); + return nicAuxiliaryService.savePlaceholderNic(network, ip4Address, ip6Address, ip6Cidr, ip6Gateway, reserver, vmType); } - @DB @Override - public Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, final Network.IpAddresses ipAddresses, final DataCenter dataCenter, final boolean forced) + public Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, + final Network.IpAddresses ipAddresses, final DataCenter dataCenter, final boolean forced) throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { - logger.debug("Allocating NIC for Instance {} in Network {} during import", vm, network); - String selectedIp = null; - if (ipAddresses != null && StringUtils.isNotEmpty(ipAddresses.getIp4Address())) { - if (ipAddresses.getIp4Address().equals("auto")) { - ipAddresses.setIp4Address(null); - } - selectedIp = getSelectedIpForNicImport(network, dataCenter, ipAddresses); - if (selectedIp == null && network.getGuestType() != GuestType.L2 && !_networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) { - throw new InsufficientVirtualNetworkCapacityException("Unable to acquire Guest IP address for network " + network, DataCenter.class, - network.getDataCenterId()); - } - } - final String finalSelectedIp = selectedIp; - final NicVO vo = Transaction.execute(new TransactionCallback<>() { - @Override - public NicVO doInTransaction(TransactionStatus status) { - if (StringUtils.isBlank(macAddress)) { - throw new CloudRuntimeException("Mac address not specified"); - } - String macAddressToPersist = macAddress.trim(); - if (!NetUtils.isValidMac(macAddressToPersist)) { - throw new CloudRuntimeException("Invalid mac address: " + macAddressToPersist); - } - NicVO existingNic = _nicDao.findByNetworkIdAndMacAddress(network.getId(), macAddressToPersist); - if (existingNic != null) { - macAddressToPersist = generateNewMacAddressIfForced(network, macAddressToPersist, forced); - } - NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType()); - vo.setMacAddress(macAddressToPersist); - vo.setAddressFormat(Networks.AddressFormat.Ip4); - Pair pair = getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, finalSelectedIp); - String gateway = pair.first(); - String netmask = pair.second(); - if (NetUtils.isValidIp4(finalSelectedIp) && StringUtils.isNotEmpty(gateway)) { - vo.setIPv4Address(finalSelectedIp); - vo.setIPv4Gateway(gateway); - vo.setIPv4Netmask(netmask); - } - vo.setBroadcastUri(network.getBroadcastUri()); - vo.setMode(network.getMode()); - vo.setState(Nic.State.Reserved); - vo.setReservationStrategy(ReservationStrategy.Start); - vo.setReservationId(UUID.randomUUID().toString()); - vo.setIsolationUri(network.getBroadcastUri()); - vo.setDeviceId(deviceId); - vo.setDefaultNic(isDefaultNic); - vo = _nicDao.persist(vo); - - int count = 1; - if (vo.getVmType() == VirtualMachine.Type.User) { - logger.debug("Changing active number of nics for network {} on {}", network, count); - _networksDao.changeActiveNicsBy(network.getId(), count); - } - if (vo.getVmType() == VirtualMachine.Type.User - || vo.getVmType() == VirtualMachine.Type.DomainRouter && _networksDao.findById(network.getId()).getTrafficType() == TrafficType.Guest) { - _networksDao.setCheckForGc(network.getId()); - } - if (vm.getType() == Type.DomainRouter) { - Pair networks = getGuestNetworkRouterAndVpcDetails(vm.getId()); - setMtuDetailsInVRNic(networks, network, vo); - } - - return vo; - } - }); - - if (selectedIp != null && GuestType.Shared.equals(network.getGuestType())) { - IPAddressVO ipAddressVO = _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), selectedIp); - if (ipAddressVO != null && IpAddress.State.Free.equals(ipAddressVO.getState())) { - ipAddressVO.setState(IPAddressVO.State.Allocated); - ipAddressVO.setAllocatedTime(new Date()); - Account account = _accountDao.findById(vm.getAccountId()); - ipAddressVO.setAllocatedInDomainId(account.getDomainId()); - ipAddressVO.setAllocatedToAccountId(account.getId()); - _ipAddressDao.update(ipAddressVO.getId(), ipAddressVO); - } - } - - final Integer networkRate = _networkModel.getNetworkRate(network.getId(), vm.getId()); - final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, _networkModel.isSecurityGroupSupportedInNetwork(network), - _networkModel.getNetworkTag(vm.getHypervisorType(), network)); - - return new Pair<>(vmNic, Integer.valueOf(deviceId)); - } - - protected String getSelectedIpForNicImport(Network network, DataCenter dataCenter, Network.IpAddresses ipAddresses) { - if (network.getGuestType() == GuestType.L2) { - return null; - } - return GuestType.Shared.equals(network.getGuestType()) ? - getSelectedIpForNicImportOnSharedNetwork(ipAddresses.getIp4Address(), network, dataCenter): - _ipAddrMgr.acquireGuestIpAddress(network, ipAddresses.getIp4Address()); - } - - protected String getSelectedIpForNicImportOnSharedNetwork(String requestedIp, Network network, DataCenter dataCenter) { - IPAddressVO ipAddressVO = StringUtils.isBlank(requestedIp) ? - _ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(network.getId(), dataCenter.getId(), IpAddress.State.Free): - _ipAddressDao.findByIpAndSourceNetworkId(network.getId(), requestedIp); - if (ipAddressVO == null || ipAddressVO.getState() != IpAddress.State.Free) { - String msg = String.format("Cannot find a free IP to assign to VM NIC on network %s", network.getName()); - logger.error(msg); - throw new CloudRuntimeException(msg); - } - return ipAddressVO.getAddress() != null ? ipAddressVO.getAddress().addr() : null; - } - - /** - * Obtain the gateway and netmask for a VM NIC to import - * If the VM to import is on a Basic Zone, then obtain the information from the vlan table instead of the network - */ - protected Pair getNetworkGatewayAndNetmaskForNicImport(Network network, DataCenter dataCenter, String selectedIp) { - String gateway = network.getGateway(); - String netmask = StringUtils.isNotEmpty(network.getCidr()) ? NetUtils.cidr2Netmask(network.getCidr()) : null; - if (dataCenter.getNetworkType() == NetworkType.Basic) { - IPAddressVO freeIp = _ipAddressDao.findByIp(selectedIp); - if (freeIp != null) { - VlanVO vlan = _vlanDao.findById(freeIp.getVlanId()); - gateway = vlan != null ? vlan.getVlanGateway() : null; - netmask = vlan != null ? vlan.getVlanNetmask() : null; - } - } - return new Pair<>(gateway, netmask); - } - - private String generateNewMacAddressIfForced(Network network, String macAddress, boolean forced) { - if (!forced) { - throw new CloudRuntimeException("NIC with MAC address " + macAddress + " exists on network " + network + - " and forced flag is disabled"); - } - try { - logger.debug("Generating a new mac address on network {} as the mac address {} already exists", network, macAddress); - String newMacAddress = _networkModel.getNextAvailableMacAddressInNetwork(network.getId()); - logger.debug("Successfully generated the mac address {}, using it instead of the conflicting address {}", newMacAddress, macAddress); - return newMacAddress; - } catch (InsufficientAddressCapacityException e) { - String msg = String.format("Could not generate a new mac address on network %s", network); - logger.error(msg); - throw new CloudRuntimeException(msg); - } + return nicImportService.importNic(macAddress, deviceId, network, isDefaultNic, vm, ipAddresses, dataCenter, forced); } @Override public void unmanageNics(VirtualMachineProfile vm) { - logger.debug("Unmanaging NICs for VM: {}", vm); - - VirtualMachine virtualMachine = vm.getVirtualMachine(); - final List nics = _nicDao.listByVmId(vm.getId()); - for (final NicVO nic : nics) { - removeNic(vm, nic); - NetworkVO network = _networksDao.findById(nic.getNetworkId()); - if (virtualMachine.getState() != VirtualMachine.State.Stopped) { - UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, virtualMachine.getAccountId(), virtualMachine.getDataCenterId(), virtualMachine.getId(), - Long.toString(nic.getId()), network.getNetworkOfferingId(), null, 0L, virtualMachine.getClass().getName(), virtualMachine.getUuid(), virtualMachine.isDisplay()); - } - } + nicAuxiliaryService.unmanageNics(vm, this::removeNic); } @Override public void expungeLbVmRefs(List vmIds, Long batchSize) { - if (CollectionUtils.isEmpty(networkElements) || CollectionUtils.isEmpty(vmIds)) { - return; - } - for (NetworkElement element : networkElements) { - if (element instanceof LoadBalancingServiceProvider) { - LoadBalancingServiceProvider lbProvider = (LoadBalancingServiceProvider)element; - lbProvider.expungeLbVmRefs(vmIds, batchSize); - } - } + nicAuxiliaryService.expungeLbVmRefs(networkElements, vmIds, batchSize); } - @Override public String getConfigComponentName() { return NetworkOrchestrationService.class.getSimpleName(); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingService.java new file mode 100644 index 000000000000..6ff3e66d0ca3 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingService.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; +import java.util.Map; + +import com.cloud.network.Network.Provider; +import com.cloud.offering.NetworkOffering; + +public interface NetworkProviderMappingService { + + Map finalizeServicesAndProvidersForNetwork(NetworkOffering offering, Long physicalNetworkId); + + List getNetworkProviders(long networkId); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingServiceImpl.java new file mode 100644 index 000000000000..77e375b5b9d1 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingServiceImpl.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.exception.UnsupportedServiceException; +import com.cloud.network.Network.Provider; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingServiceMapVO; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; + +@Component +public class NetworkProviderMappingServiceImpl implements NetworkProviderMappingService { + + @Inject + protected NetworkOfferingServiceMapDao networkOfferingServiceMapDao; + + @Inject + protected NetworkModel networkModel; + + @Inject + protected PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao; + + @Inject + protected NetworkServiceMapDao networkServiceMapDao; + + @Override + public Map finalizeServicesAndProvidersForNetwork(final NetworkOffering offering, final Long physicalNetworkId) { + final Map svcProviders = new HashMap<>(); + final Map> providerSvcs = new HashMap<>(); + final List servicesMap = networkOfferingServiceMapDao.listByNetworkOfferingId(offering.getId()); + + final boolean checkPhysicalNetwork = physicalNetworkId != null; + + for (final NetworkOfferingServiceMapVO serviceMap : servicesMap) { + if (svcProviders.containsKey(serviceMap.getService())) { + // FIXME - right now we pick up the first provider from the list, need to add more logic based on + // provider load, etc + continue; + } + + final String service = serviceMap.getService(); + String provider = serviceMap.getProvider(); + + if (provider == null) { + provider = networkModel.getDefaultUniqueProviderForService(service).getName(); + } + + if (checkPhysicalNetwork && !physicalNetworkServiceProviderDao.isServiceProviderEnabled(physicalNetworkId, provider, service)) { + throw new UnsupportedServiceException("Provider " + provider + " is either not enabled or doesn't " + "support service " + service + " in physical network id=" + + physicalNetworkId); + } + + svcProviders.put(service, provider); + List l = providerSvcs.get(provider); + if (l == null) { + providerSvcs.put(provider, l = new ArrayList<>()); + } + l.add(service); + } + + return svcProviders; + } + + @Override + public List getNetworkProviders(final long networkId) { + final List providerNames = networkServiceMapDao.getDistinctProviders(networkId); + final List providers = new ArrayList<>(); + for (final String providerName : providerNames) { + providers.add(Provider.getProvider(providerName)); + } + + return providers; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionService.java new file mode 100644 index 000000000000..ce975b53f7d5 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionService.java @@ -0,0 +1,116 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; + +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.DnsServiceProvider; +import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.StaticNatServiceProvider; +import com.cloud.network.element.UserDataServiceProvider; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; + +/** + * Pure lookup helpers that map a {@link Network} (plus a + * {@link Service}) onto the {@link Provider}(s) or {@link NetworkElement}(s) + * that actually implement the service for that network. + * + *

Extracted from {@link NetworkOrchestrator} as part of the Phase 4 + * Spring-component decomposition. The orchestrator continues to expose + * the corresponding methods on {@link + * org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService}, + * each as a one-line wrapper that delegates here, so existing call + * sites and test spies keep working unchanged. + * + *

Nothing in here mutates state -- every method is a read-only lookup + * over {@code NetworkServiceMapDao}, {@code NetworkModel}, + * {@code NetworkOfferingDetailsDao} and the {@code EntityManager}. + */ +public interface NetworkProviderResolutionService { + + /** + * Return the list of {@link Provider}s configured for {@code service} + * on the given {@code network}, or {@code null} if the network does + * not have any provider for that service. + */ + List getProvidersForServiceInNetwork(Network network, Service service); + + /** + * Return the {@link NetworkElement}s implementing {@code service} on + * the given {@code network}. + * + *

Returns {@code null} when no provider is configured for the + * service. For every service except {@link Service#Lb} the result + * contains a single element; an error is logged and {@code null} + * returned if more than one provider is found for a non-LB service. + */ + List getElementForServiceInNetwork(Network network, Service service); + + /** + * Return the {@link StaticNatServiceProvider} for {@code network}. + * Only one provider per static-NAT service is supported. + */ + StaticNatServiceProvider getStaticNatProviderForNetwork(Network network); + + /** + * Return the {@link LoadBalancingServiceProvider} for {@code network} + * matching the requested {@link Scheme}. When the network offering + * configures separate public and internal LB providers, the matching + * one for {@code lbScheme} is resolved via + * {@code NetworkOfferingDetailsDao}; otherwise the single configured + * LB element is returned. + * + * @throws com.cloud.exception.InvalidParameterValueException + * when multiple LB providers are configured but no provider + * is recorded in the offering details for the requested scheme. + */ + LoadBalancingServiceProvider getLoadBalancingProviderForNetwork(Network network, Scheme lbScheme); + + /** + * Return the {@link UserDataServiceProvider} responsible for + * password reset on the network (delegates to the {@link + * Service#UserData} provider). Returns {@code null} if no provider + * is configured. + */ + UserDataServiceProvider getPasswordResetProvider(Network network); + + /** + * Return the {@link UserDataServiceProvider} responsible for + * SSH-key reset on the network (delegates to the {@link + * Service#UserData} provider). Returns {@code null} if no provider + * is configured. + */ + UserDataServiceProvider getSSHKeyResetProvider(Network network); + + /** + * Return the {@link DhcpServiceProvider} for {@code network}, or + * {@code null} when no DHCP provider is configured or the + * configured provider's element is not a {@link DhcpServiceProvider}. + */ + DhcpServiceProvider getDhcpServiceProvider(Network network); + + /** + * Return the {@link DnsServiceProvider} for {@code network}, or + * {@code null} when no DNS provider is configured. + */ + DnsServiceProvider getDnsServiceProvider(Network network); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionServiceImpl.java new file mode 100644 index 000000000000..83d9b6384440 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionServiceImpl.java @@ -0,0 +1,211 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkServiceMapVO; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.DnsServiceProvider; +import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.StaticNatServiceProvider; +import com.cloud.network.element.UserDataServiceProvider; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingDetailsDao; +import com.cloud.utils.db.EntityManager; + +/** + * Lookups that resolve which network {@link Provider} (or {@link + * NetworkElement}) implements a given {@link Service} on a particular + * {@link Network} -- extracted from {@link NetworkOrchestrator}. + * + * @see NetworkProviderResolutionService + */ +@Component +public class NetworkProviderResolutionServiceImpl implements NetworkProviderResolutionService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NetworkServiceMapDao networkServiceMapDao; + + @Inject + protected NetworkModel networkModel; + + @Inject + protected NetworkOfferingDetailsDao networkOfferingDetailsDao; + + @Inject + protected EntityManager entityManager; + + @Override + public List getProvidersForServiceInNetwork(final Network network, final Service service) { + final Map> service2ProviderMap = getServiceProvidersMap(network.getId()); + if (service2ProviderMap.get(service) != null) { + return new ArrayList<>(service2ProviderMap.get(service)); + } + return null; + } + + @Override + public List getElementForServiceInNetwork(final Network network, final Service service) { + final List elements = new ArrayList<>(); + final List providers = getProvidersForServiceInNetwork(network, service); + // Only support one provider now (except for Lb) + if (providers == null) { + logger.error("Cannot find {} provider for network {}", service.getName(), network); + return null; + } + if (providers.size() != 1 && service != Service.Lb) { + // support more than one LB providers only + logger.error("Found {} {} providers for network! {}", providers.size(), service.getName(), network); + return null; + } + + for (final Provider provider : providers) { + final NetworkElement element = networkModel.getElementImplementingProvider(provider.getName()); + logger.info("Let {} handle {} in network {}", element.getName(), service.getName(), network); + elements.add(element); + } + return elements; + } + + @Override + public StaticNatServiceProvider getStaticNatProviderForNetwork(final Network network) { + // only one provider per Static nat service is supported + final NetworkElement element = getElementForServiceInNetwork(network, Service.StaticNat).get(0); + assert element instanceof StaticNatServiceProvider; + return (StaticNatServiceProvider) element; + } + + @Override + public LoadBalancingServiceProvider getLoadBalancingProviderForNetwork(final Network network, final Scheme lbScheme) { + final List lbElements = getElementForServiceInNetwork(network, Service.Lb); + NetworkElement lbElement = null; + if (lbElements.size() > 1) { + String providerName; + // get network offering details + final NetworkOffering off = entityManager.findById(NetworkOffering.class, network.getNetworkOfferingId()); + if (lbScheme == Scheme.Public) { + providerName = networkOfferingDetailsDao.getDetail(off.getId(), NetworkOffering.Detail.PublicLbProvider); + } else { + providerName = networkOfferingDetailsDao.getDetail(off.getId(), NetworkOffering.Detail.InternalLbProvider); + } + if (providerName == null) { + throw new InvalidParameterValueException("Can't find Lb provider supporting scheme " + lbScheme.toString() + " in network " + network); + } + lbElement = networkModel.getElementImplementingProvider(providerName); + } else if (lbElements.size() == 1) { + lbElement = lbElements.get(0); + } + + assert lbElement != null; + assert lbElement instanceof LoadBalancingServiceProvider; + return (LoadBalancingServiceProvider) lbElement; + } + + @Override + public UserDataServiceProvider getPasswordResetProvider(final Network network) { + final String passwordProvider = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.UserData); + + if (passwordProvider == null) { + logger.debug("Network {} doesn't support service {}", network, Service.UserData.getName()); + return null; + } + + return (UserDataServiceProvider) networkModel.getElementImplementingProvider(passwordProvider); + } + + @Override + public UserDataServiceProvider getSSHKeyResetProvider(final Network network) { + final String sshKeyProvider = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.UserData); + + if (sshKeyProvider == null) { + logger.debug("Network {} doesn't support service", network, Service.UserData.getName()); + return null; + } + + return (UserDataServiceProvider) networkModel.getElementImplementingProvider(sshKeyProvider); + } + + @Override + public DhcpServiceProvider getDhcpServiceProvider(final Network network) { + final String dhcpProvider = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.Dhcp); + + if (dhcpProvider == null) { + logger.debug("Network {} doesn't support service {}", network, Service.Dhcp.getName()); + return null; + } + + final NetworkElement element = networkModel.getElementImplementingProvider(dhcpProvider); + if (element instanceof DhcpServiceProvider) { + return (DhcpServiceProvider) element; + } else { + return null; + } + } + + @Override + public DnsServiceProvider getDnsServiceProvider(final Network network) { + final String dnsProvider = networkServiceMapDao.getProviderForServiceInNetwork(network.getId(), Service.Dns); + + if (dnsProvider == null) { + logger.debug("Network {} doesn't support service {}", network, Service.Dhcp.getName()); + return null; + } + + return (DnsServiceProvider) networkModel.getElementImplementingProvider(dnsProvider); + } + + /** + * Build a Service -> Providers map for the given network by reading + * {@code NetworkServiceMapDao}. Internal helper used by {@link + * #getProvidersForServiceInNetwork(Network, Service)}. + */ + protected Map> getServiceProvidersMap(final long networkId) { + final Map> map = new HashMap<>(); + final List nsms = networkServiceMapDao.getServicesInNetwork(networkId); + for (final NetworkServiceMapVO nsm : nsms) { + Set providers = map.get(Service.getService(nsm.getService())); + if (providers == null) { + providers = new HashSet<>(); + } + providers.add(Provider.getProvider(nsm.getProvider())); + map.put(Service.getService(nsm.getService()), providers); + } + return map; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupService.java new file mode 100644 index 000000000000..2388fdcb84c9 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupService.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.network.Network; +import com.cloud.user.Account; + +public interface NetworkResourceCleanupService { + + boolean cleanupNetworkResources(long networkId, Account caller, long callerUserId); + + boolean shutdownNetworkResources(Network network, Account caller, long callerUserId); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupServiceImpl.java new file mode 100644 index 000000000000..7b47b4c97508 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupServiceImpl.java @@ -0,0 +1,368 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.network.RoutedIpv4Manager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRule.Purpose; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.network.rules.PortForwardingRuleVO; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.rules.StaticNatRule; +import com.cloud.network.rules.StaticNatRuleImpl; +import com.cloud.network.rules.dao.PortForwardingRulesDao; +import com.cloud.network.vpc.NetworkACLManager; +import com.cloud.network.vpc.VpcManager; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.exception.CloudRuntimeException; + +@Component +public class NetworkResourceCleanupServiceImpl implements NetworkResourceCleanupService { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NetworkDao networkDao; + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected RoutedIpv4Manager routedIpv4Manager; + @Inject + protected RulesManager rulesManager; + @Inject + protected LoadBalancingRulesManager loadBalancingRulesManager; + @Inject + protected FirewallManager firewallManager; + @Inject + protected NetworkACLManager networkACLManager; + @Inject + protected IPAddressDao ipAddressDao; + @Inject + protected IpAddressManager ipAddressManager; + @Inject + protected VpcManager vpcManager; + @Inject + protected AnnotationDao annotationDao; + @Inject + protected PortForwardingRulesDao portForwardingRulesDao; + @Inject + protected FirewallRulesDao firewallRulesDao; + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected NetworkModel networkModel; + @Inject + protected VlanDao vlanDao; + + @Override + public boolean cleanupNetworkResources(final long networkId, final Account caller, final long callerUserId) { + boolean success = true; + final NetworkVO network = networkDao.findById(networkId); + final NetworkOfferingVO networkOffering = networkOfferingDao.findById(network.getNetworkOfferingId()); + + //remove BGP peers from the network + if (routedIpv4Manager.removeBgpPeersFromNetwork(network) != null) { + logger.debug("Successfully removed BGP peers from network id={}", networkId); + } else { + success = false; + logger.warn("Failed to remove BGP peers from network as a part of network id={} cleanup", networkId); + } + + //remove all PF/Static Nat rules for the network + try { + if (rulesManager.revokeAllPFStaticNatRulesForNetwork(networkId, callerUserId, caller)) { + logger.debug("Successfully cleaned up portForwarding/staticNat rules for network {}", network); + } else { + success = false; + logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup", network); + } + } catch (final ResourceUnavailableException ex) { + success = false; + // shouldn't even come here as network is being cleaned up after all network elements are shutdown + logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup due to resourceUnavailable", network, ex); + } + + //remove all LB rules for the network + if (loadBalancingRulesManager.removeAllLoadBalanacersForNetwork(networkId, caller, callerUserId)) { + logger.debug("Successfully cleaned up load balancing rules for network {}", network); + } else { + // shouldn't even come here as network is being cleaned up after all network elements are shutdown + success = false; + logger.warn("Failed to cleanup LB rules as a part of network {} cleanup", network); + } + + //revoke all firewall rules for the network + try { + if (firewallManager.revokeAllFirewallRulesForNetwork(network, callerUserId, caller)) { + logger.debug("Successfully cleaned up firewallRules rules for network {}", network); + } else { + success = false; + logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup", network); + } + } catch (final ResourceUnavailableException ex) { + success = false; + // shouldn't even come here as network is being cleaned up after all network elements are shutdown + logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup due to resourceUnavailable", network, ex); + } + + //revoke all network ACLs for network + try { + if (networkACLManager.revokeACLItemsForNetwork(networkId)) { + logger.debug("Successfully cleaned up NetworkACLs for network {}", network); + } else { + success = false; + logger.warn("Failed to cleanup NetworkACLs as a part of network {} cleanup", network); + } + } catch (final ResourceUnavailableException ex) { + success = false; + logger.warn("Failed to cleanup Network ACLs as a part of network {} cleanup due to resourceUnavailable ", network, ex); + } + + //release all ip addresses + final List ipsToRelease = ipAddressDao.listByAssociatedNetwork(networkId, null); + for (final IPAddressVO ipToRelease : ipsToRelease) { + if (ipToRelease.getVpcId() == null) { + if (!ipToRelease.isPortable()) { + final IPAddressVO ip = ipAddressManager.markIpAsUnavailable(ipToRelease.getId()); + assert ip != null : "Unable to mark the ip address id=" + ipToRelease.getId() + " as unavailable."; + } else { + // portable IP address are associated with owner, until explicitly requested to be disassociated + // so as part of network clean up just break IP association with guest network + ipToRelease.setAssociatedWithNetworkId(null); + ipAddressDao.update(ipToRelease.getId(), ipToRelease); + logger.debug("Portable IP address {} is no longer associated with any network", ipToRelease); + } + } else { + vpcManager.unassignIPFromVpcNetwork(ipToRelease, network); + } + } + + try { + if (!ipAddressManager.applyIpAssociations(network, true)) { + logger.warn("Unable to apply ip address associations for {}", network); + success = false; + } + } catch (final ResourceUnavailableException e) { + throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e); + } + + annotationDao.removeByEntityType(AnnotationService.EntityType.NETWORK.name(), network.getUuid()); + + return success; + } + + @Override + public boolean shutdownNetworkResources(final Network network, final Account caller, final long callerUserId) { + // This method cleans up network rules on the backend w/o touching them in the DB + boolean success = true; + + // Mark all PF rules as revoked and apply them on the backend (not in the DB) + final List pfRules = portForwardingRulesDao.listByNetwork(network.getId()); + logger.debug("Releasing {} port forwarding rules for network id={} as a part of shutdownNetworkRules.", pfRules.size(), network); + + for (final PortForwardingRuleVO pfRule : pfRules) { + logger.trace("Marking pf rule {} with Revoke state", pfRule); + pfRule.setState(FirewallRule.State.Revoke); + } + + try { + if (!firewallManager.applyRules(pfRules, true, false)) { + logger.warn("Failed to cleanup pf rules as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup pf rules as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + // Mark all static rules as revoked and apply them on the backend (not in the DB) + final List firewallStaticNatRules = firewallRulesDao.listByNetworkAndPurpose(network.getId(), Purpose.StaticNat); + final List staticNatRules = new ArrayList<>(); + logger.debug("Releasing {} static nat rules for network {} as a part of shutdownNetworkRules", firewallStaticNatRules.size(), network); + + for (final FirewallRuleVO firewallStaticNatRule : firewallStaticNatRules) { + logger.trace("Marking static nat rule {} with Revoke state", firewallStaticNatRule); + final IpAddress ip = ipAddressDao.findById(firewallStaticNatRule.getSourceIpAddressId()); + final FirewallRuleVO ruleVO = firewallRulesDao.findById(firewallStaticNatRule.getId()); + + if (ip == null || !ip.isOneToOneNat() || ip.getAssociatedWithVmId() == null) { + throw new InvalidParameterValueException(String.format("Source ip address of the rule %s is not static nat enabled", firewallStaticNatRule)); + } + + //String dstIp = _networkModel.getIpInNetwork(ip.getAssociatedWithVmId(), firewallStaticNatRule.getNetworkId()); + ruleVO.setState(FirewallRule.State.Revoke); + staticNatRules.add(new StaticNatRuleImpl(ruleVO, ip.getVmIp())); + } + + try { + if (!firewallManager.applyRules(staticNatRules, true, false)) { + logger.warn("Failed to cleanup static nat rules as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup static nat rules as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + try { + if (!loadBalancingRulesManager.revokeLoadBalancersForNetwork(network, Scheme.Public)) { + logger.warn("Failed to cleanup public lb rules as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup public lb rules as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + try { + if (!loadBalancingRulesManager.revokeLoadBalancersForNetwork(network, Scheme.Internal)) { + logger.warn("Failed to cleanup internal lb rules as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup public lb rules as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + // revoke all firewall rules for the network w/o applying them on the DB + final List firewallRules = firewallRulesDao.listByNetworkPurposeTrafficType(network.getId(), Purpose.Firewall, FirewallRule.TrafficType.Ingress); + logger.debug("Releasing firewall ingress rules for network {} as a part of shutdownNetworkRules", firewallRules.size(), network); + + for (final FirewallRuleVO firewallRule : firewallRules) { + logger.trace("Marking firewall ingress rule {} with Revoke state", firewallRule); + firewallRule.setState(FirewallRule.State.Revoke); + } + + try { + if (!firewallManager.applyRules(firewallRules, true, false)) { + logger.warn("Failed to cleanup firewall ingress rules as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup firewall ingress rules as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + final List firewallEgressRules = firewallRulesDao.listByNetworkPurposeTrafficType(network.getId(), Purpose.Firewall, FirewallRule.TrafficType.Egress); + logger.debug("Releasing {} firewall egress rules for network {} as a part of shutdownNetworkRules", firewallEgressRules.size(), network); + + try { + // delete default egress rule + final DataCenter zone = dataCenterDao.findById(network.getDataCenterId()); + if (networkModel.areServicesSupportedInNetwork(network.getId(), Service.Firewall) + && (network.getGuestType() == Network.GuestType.Isolated || network.getGuestType() == Network.GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) { + // add default egress rule to accept the traffic + firewallManager.applyDefaultEgressFirewallRule(network.getId(), networkModel.getNetworkEgressDefaultPolicy(network.getId()), false); + } + + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup firewall default egress rule as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + for (final FirewallRuleVO firewallRule : firewallEgressRules) { + logger.trace("Marking firewall egress rule {} with Revoke state", firewallRule); + firewallRule.setState(FirewallRule.State.Revoke); + } + + try { + if (!firewallManager.applyRules(firewallEgressRules, true, false)) { + logger.warn("Failed to cleanup firewall egress rules as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup firewall egress rules as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + if (network.getVpcId() != null) { + logger.debug("Releasing Network ACL Items for network {} as a part of shutdownNetworkRules", network); + + try { + //revoke all Network ACLs for the network w/o applying them in the DB + if (!networkACLManager.revokeACLItemsForNetwork(network.getId())) { + logger.warn("Failed to cleanup network ACLs as a part of shutdownNetworkRules"); + success = false; + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup network ACLs as a part of shutdownNetworkRules due to ", ex); + success = false; + } + + } + + //release all static nats for the network + if (!rulesManager.applyStaticNatForNetwork(network, false, caller, true)) { + logger.warn("Failed to disable static nats as part of shutdownNetworkRules for network {}", network); + success = false; + } + + // Get all ip addresses, mark as releasing and release them on the backend + final List userIps = ipAddressDao.listByAssociatedNetwork(network.getId(), null); + final List publicIpsToRelease = new ArrayList<>(); + if (userIps != null && !userIps.isEmpty()) { + for (final IPAddressVO userIp : userIps) { + userIp.setState(IpAddress.State.Releasing); + final PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, vlanDao.findById(userIp.getVlanId())); + publicIpsToRelease.add(publicIp); + } + } + + try { + if (!ipAddressManager.applyIpAssociations(network, true, true, publicIpsToRelease)) { + logger.warn("Unable to apply ip address associations for {} as a part of shutdownNetworkRules", network); + success = false; + } + } catch (final ResourceUnavailableException e) { + throw new CloudRuntimeException("We should never get to here because we used true when applyIpAssociations", e); + } + + return success; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingService.java new file mode 100644 index 000000000000..6628af7a99f7 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingService.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.user.Account; + +public interface NetworkRuleReprogrammingService { + + boolean reprogramNetworkRules(long networkId, Account caller, Network network) throws ResourceUnavailableException; +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingServiceImpl.java new file mode 100644 index 000000000000..da598dd975e4 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingServiceImpl.java @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.bgp.BGPService; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRule.Purpose; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.vpc.NetworkACLManager; +import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; + +@Component +public class NetworkRuleReprogrammingServiceImpl implements NetworkRuleReprogrammingService { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NetworkModel networkModel; + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected FirewallRulesDao firewallRulesDao; + @Inject + protected FirewallManager firewallManager; + @Inject + protected IpAddressManager ipAddressManager; + @Inject + protected BGPService bgpService; + @Inject + protected RulesManager rulesManager; + @Inject + protected LoadBalancingRulesManager lbManager; + @Inject + protected RemoteAccessVpnService vpnManager; + @Inject + protected NetworkACLManager networkACLManager; + + @Override + public boolean reprogramNetworkRules(final long networkId, final Account caller, final Network network) throws ResourceUnavailableException { + boolean success = true; + + //Apply egress rules first to effect the egress policy early on the guest traffic + final List firewallEgressRulesToApply = firewallRulesDao.listByNetworkPurposeTrafficType(networkId, Purpose.Firewall, FirewallRule.TrafficType.Egress); + final NetworkOfferingVO offering = networkOfferingDao.findById(network.getNetworkOfferingId()); + final DataCenter zone = dataCenterDao.findById(network.getDataCenterId()); + if (networkModel.areServicesSupportedInNetwork(network.getId(), Service.Firewall) && networkModel.areServicesSupportedInNetwork(network.getId(), Service.Firewall) + && (network.getGuestType() == Network.GuestType.Isolated || network.getGuestType() == Network.GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) { + // add default egress rule to accept the traffic + firewallManager.applyDefaultEgressFirewallRule(network.getId(), offering.isEgressDefaultPolicy(), true); + } + if (!firewallManager.applyFirewallRules(firewallEgressRulesToApply, false, caller)) { + logger.warn("Failed to reapply firewall Egress rule(s) as a part of Network {} restart", network); + success = false; + } + + // associate all ip addresses + if (!ipAddressManager.applyIpAssociations(network, false)) { + logger.warn("Failed to apply IP addresses as a part of Network {} restart", network); + success = false; + } + + // apply BGP settings + if (!bgpService.applyBgpPeers(network, false)) { + logger.warn("Failed to apply bpg peers as a part of network {} restart", network); + success = false; + } + + + // apply static nat + if (!rulesManager.applyStaticNatsForNetwork(network, false, caller)) { + logger.warn("Failed to apply static nats a part of network {} restart", network); + success = false; + } + + // apply firewall rules + final List firewallIngressRulesToApply = firewallRulesDao.listByNetworkPurposeTrafficType(networkId, Purpose.Firewall, FirewallRule.TrafficType.Ingress); + if (!firewallManager.applyFirewallRules(firewallIngressRulesToApply, false, caller)) { + logger.warn("Failed to reapply Ingress firewall rule(s) as a part of network {} restart", network); + success = false; + } + + // apply port forwarding rules + if (!rulesManager.applyPortForwardingRulesForNetwork(networkId, false, caller)) { + logger.warn("Failed to reapply port forwarding rule(s) as a part of network {} restart", network); + success = false; + } + + // apply static nat rules + if (!rulesManager.applyStaticNatRulesForNetwork(networkId, false, caller)) { + logger.warn("Failed to reapply static nat rule(s) as a part of network {} restart", network); + success = false; + } + + // apply public load balancer rules + if (!lbManager.applyLoadBalancersForNetwork(network, Scheme.Public)) { + logger.warn("Failed to reapply Public load balancer rules as a part of network {} restart", network); + success = false; + } + + // apply internal load balancer rules + if (!lbManager.applyLoadBalancersForNetwork(network, Scheme.Internal)) { + logger.warn("Failed to reapply internal load balancer rules as a part of network {} restart", network); + success = false; + } + + // apply vpn rules + final List vpnsToReapply = vpnManager.listRemoteAccessVpns(networkId); + if (vpnsToReapply != null) { + for (final RemoteAccessVpn vpn : vpnsToReapply) { + // Start remote access vpn per ip + if (vpnManager.startRemoteAccessVpn(vpn.getServerAddressId(), false) == null) { + logger.warn("Failed to reapply vpn rules as a part of network {} restart", network); + success = false; + } + } + } + + //apply network ACLs + if (!networkACLManager.applyACLToNetwork(networkId)) { + logger.warn("Failed to reapply network ACLs as a part of of network {}", network); + success = false; + } + + return success; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupService.java new file mode 100644 index 000000000000..fc14e3d3835c --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupService.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; + +import com.cloud.network.Network; + +public interface NetworkServiceChangeCleanupService { + + List getServicesNotSupportedInNewOffering(Network network, long newNetworkOfferingId); + + void cleanupConfigForServicesInNetwork(List services, Network network); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupServiceImpl.java new file mode 100644 index 000000000000..010216688da7 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupServiceImpl.java @@ -0,0 +1,181 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkServiceMapVO; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; + +@Component +public class NetworkServiceChangeCleanupServiceImpl implements NetworkServiceChangeCleanupService { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected NetworkOfferingServiceMapDao networkOfferingServiceMapDao; + @Inject + protected NetworkServiceMapDao networkServiceMapDao; + @Inject + protected AccountDao accountDao; + @Inject + protected IPAddressDao ipAddressDao; + @Inject + protected RulesManager rulesManager; + @Inject + protected LoadBalancingRulesManager lbManager; + @Inject + protected FirewallManager firewallManager; + @Inject + protected RemoteAccessVpnDao remoteAccessVpnDao; + @Inject + protected RemoteAccessVpnService vpnManager; + + @Override + public List getServicesNotSupportedInNewOffering(final Network network, final long newNetworkOfferingId) { + final NetworkOffering offering = networkOfferingDao.findById(newNetworkOfferingId); + final List services = networkOfferingServiceMapDao.listServicesForNetworkOffering(offering.getId()); + final List serviceMap = networkServiceMapDao.getServicesInNetwork(network.getId()); + final List servicesNotInNewOffering = new ArrayList<>(); + for (final NetworkServiceMapVO serviceVO : serviceMap) { + boolean inlist = false; + for (final String service : services) { + if (serviceVO.getService().equalsIgnoreCase(service)) { + inlist = true; + break; + } + } + if (!inlist) { + //ignore Gateway service as this has no effect on the + //behaviour of network. + if (!serviceVO.getService().equalsIgnoreCase(Service.Gateway.getName())) + servicesNotInNewOffering.add(serviceVO.getService()); + } + } + return servicesNotInNewOffering; + } + + @Override + public void cleanupConfigForServicesInNetwork(final List services, final Network network) { + final long networkId = network.getId(); + final Account caller = accountDao.findById(Account.ACCOUNT_ID_SYSTEM); + final long userId = User.UID_SYSTEM; + //remove all PF/Static Nat rules for the network + logger.info("Services: {} are no longer supported in network: {} after applying new network offering: {} removing the related configuration", + services::toString, network::toString, () -> networkOfferingDao.findById(network.getNetworkOfferingId())); + if (services.contains(Service.StaticNat.getName()) || services.contains(Service.PortForwarding.getName())) { + try { + if (rulesManager.revokeAllPFStaticNatRulesForNetwork(networkId, userId, caller)) { + logger.debug("Successfully cleaned up portForwarding/staticNat rules for network {}", network); + } else { + logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup", network); + } + if (services.contains(Service.StaticNat.getName())) { + //removing static nat configured on ips. + //optimizing the db operations using transaction. + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(final TransactionStatus status) { + final List ips = ipAddressDao.listStaticNatPublicIps(network.getId()); + for (final IPAddressVO ip : ips) { + ip.setOneToOneNat(false); + ip.setAssociatedWithVmId(null); + ip.setVmIp(null); + ip.setForRouter(false); + ipAddressDao.update(ip.getId(), ip); + } + } + }); + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to release portForwarding/StaticNat rules as a part of network {} cleanup due to resourceUnavailable", network, ex); + } + } + if (services.contains(Service.SourceNat.getName())) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(final TransactionStatus status) { + final List ips = ipAddressDao.listByAssociatedNetwork(network.getId(), true); + //removing static nat configured on ips. + for (final IPAddressVO ip : ips) { + ip.setSourceNat(false); + ipAddressDao.update(ip.getId(), ip); + } + } + }); + } + if (services.contains(Service.Lb.getName())) { + //remove all LB rules for the network + if (lbManager.removeAllLoadBalanacersForNetwork(networkId, caller, userId)) { + logger.debug("Successfully cleaned up load balancing rules for network {}", network); + } else { + logger.warn("Failed to cleanup LB rules as a part of network {} cleanup", network); + } + } + + if (services.contains(Service.Firewall.getName())) { + //revoke all firewall rules for the network + try { + if (firewallManager.revokeAllFirewallRulesForNetwork(network, userId, caller)) { + logger.debug("Successfully cleaned up firewallRules rules for network {}", network); + } else { + logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup", network); + } + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup Firewall rules as a part of network {} cleanup due to resourceUnavailable", network, ex); + } + } + + //do not remove vpn service for vpc networks. + if (services.contains(Service.Vpn.getName()) && network.getVpcId() == null) { + final RemoteAccessVpnVO vpn = remoteAccessVpnDao.findByAccountAndNetwork(network.getAccountId(), networkId); + try { + vpnManager.destroyRemoteAccessVpnForIp(vpn.getServerAddressId(), caller, true); + } catch (final ResourceUnavailableException ex) { + logger.warn("Failed to cleanup remote access vpn resources of network: {} due to Exception: {}", network, ex); + } + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceService.java new file mode 100644 index 000000000000..bda826390445 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceService.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.network.Network; + +public interface NetworkUpdateSequenceService { + + boolean canUpdateInSequence(Network network, boolean forced); + + void configureUpdateInSequence(Network network); + + int getResourceCount(Network network); + + void finalizeUpdateInSequence(Network network, boolean success); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceServiceImpl.java new file mode 100644 index 000000000000..7618172d8f49 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceServiceImpl.java @@ -0,0 +1,112 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.RedundantResource; +import com.cloud.network.router.VirtualRouter; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.dao.DomainRouterDao; + +@Component +public class NetworkUpdateSequenceServiceImpl implements NetworkUpdateSequenceService { + + @Inject + protected NetworkServiceMapDao networkServiceMapDao; + @Inject + protected DomainRouterDao routerDao; + + protected List networkElements; + + public void setNetworkElements(final List networkElements) { + this.networkElements = networkElements; + } + + @Override + public boolean canUpdateInSequence(final Network network, final boolean forced) { + final List providers = getNetworkProviders(network.getId()); + + for (final Provider provider : providers) { + if (provider != Provider.VirtualRouter) { + throw new UnsupportedOperationException("Cannot update the network resources in sequence when providers other than virtualrouter are used"); + } + } + + final List routers = routerDao.listByNetworkAndRole(network.getId(), VirtualRouter.Role.VIRTUAL_ROUTER); + for (final DomainRouterVO router : routers) { + if (router.getRedundantState() == VirtualRouter.RedundantState.UNKNOWN && !forced) { + throw new CloudRuntimeException("Domain router: " + router.getInstanceName() + + " is in unknown state, Cannot update network. set parameter forced to true for forcing an update"); + } + } + return true; + } + + @Override + public void configureUpdateInSequence(final Network network) { + final List providers = getNetworkProviders(network.getId()); + for (final NetworkElement element : networkElements) { + if (providers.contains(element.getProvider()) && element instanceof RedundantResource) { + ((RedundantResource) element).configureResource(network); + } + } + } + + @Override + public int getResourceCount(final Network network) { + final List providers = getNetworkProviders(network.getId()); + int resourceCount = 0; + for (final NetworkElement element : networkElements) { + if (providers.contains(element.getProvider()) && element instanceof RedundantResource) { + resourceCount = ((RedundantResource) element).getResourceCount(network); + break; + } + } + return resourceCount; + } + + @Override + public void finalizeUpdateInSequence(final Network network, final boolean success) { + final List providers = getNetworkProviders(network.getId()); + for (final NetworkElement element : networkElements) { + if (providers.contains(element.getProvider()) && element instanceof RedundantResource) { + ((RedundantResource) element).finalize(network, success); + break; + } + } + } + + private List getNetworkProviders(final long networkId) { + final List providerNames = networkServiceMapDao.getDistinctProviders(networkId); + final List providers = new ArrayList<>(); + for (final String providerName : providerNames) { + providers.add(Network.Provider.getProvider(providerName)); + } + return providers; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupService.java new file mode 100644 index 000000000000..a984f682cbc9 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupService.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; + +import com.cloud.dc.VlanVO; +import com.cloud.network.dao.NetworkVO; +import com.cloud.user.Account; +import com.cloud.utils.Pair; + +public interface NetworkVlanRangeCleanupService { + + Pair> deleteVlansInNetwork(NetworkVO network, long userId, Account callerAccount); + + void publishDeletedVlanRanges(String senderAddress, List deletedVlanRangeToPublish); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupServiceImpl.java new file mode 100644 index 000000000000..84183b2a1f5a --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupServiceImpl.java @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static com.cloud.configuration.ConfigurationManager.MESSAGE_DELETE_VLAN_IP_RANGE_EVENT; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.framework.messagebus.PublishScope; +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.dao.PrivateIpDao; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.Pair; + +@Component +public class NetworkVlanRangeCleanupServiceImpl implements NetworkVlanRangeCleanupService { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected VlanDao vlanDao; + @Inject + protected ConfigurationManager configurationManager; + @Inject + protected PrivateIpDao privateIpDao; + @Inject + protected NetworkOfferingDao networkOfferingDao; + @Inject + protected DataCenterDao dataCenterDao; + @Inject + protected NetworkOfferingVlanValidationService networkOfferingVlanValidationService; + @Inject + protected MessageBus messageBus; + + @Override + public Pair> deleteVlansInNetwork(final NetworkVO network, final long userId, final Account callerAccount) { + final long networkId = network.getId(); + final List publicVlans = vlanDao.listVlansByNetworkId(networkId); + List deletedPublicVlanRange = new ArrayList<>(); + boolean result = true; + for (final VlanVO vlan : publicVlans) { + VlanVO vlanRange = configurationManager.deleteVlanAndPublicIpRange(userId, vlan.getId(), callerAccount); + if (vlanRange == null) { + logger.warn("Failed to delete vlan [id: {}, uuid: {}];", vlan.getId(), vlan.getUuid()); + result = false; + } else { + deletedPublicVlanRange.add(vlanRange); + } + } + + final int privateIpAllocCount = privateIpDao.countAllocatedByNetworkId(networkId); + if (privateIpAllocCount > 0) { + logger.warn("Can't delete Private IP range for Network {} as it has allocated IP addresses", network); + result = false; + } else { + privateIpDao.deleteByNetworkId(networkId); + logger.debug("Deleted ip range for private network {}", network); + } + + if (networkOfferingVlanValidationService.isSharedNetworkWithoutSpecifyVlan(networkOfferingDao.findById(network.getNetworkOfferingId()))) { + logger.debug("Releasing vnet for the network {}", network); + dataCenterDao.releaseVnet(BroadcastDomainType.getValue(network.getBroadcastUri()), network.getDataCenterId(), + network.getPhysicalNetworkId(), network.getAccountId(), network.getReservationId()); + } + return new Pair<>(result, deletedPublicVlanRange); + } + + @Override + public void publishDeletedVlanRanges(String senderAddress, List deletedVlanRangeToPublish) { + if (CollectionUtils.isNotEmpty(deletedVlanRangeToPublish)) { + for (VlanVO vlan : deletedVlanRangeToPublish) { + messageBus.publish(senderAddress, MESSAGE_DELETE_VLAN_IP_RANGE_EVENT, PublishScope.LOCAL, vlan); + } + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryService.java new file mode 100644 index 000000000000..2a461abff734 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryService.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; +import java.util.function.BiConsumer; + +import com.cloud.network.Network; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.vm.Nic; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; + +/** + * Small NIC helper operations extracted from {@link NetworkOrchestrator}. + */ +public interface NicAuxiliaryService { + + List listVmNics(long vmId, Long nicId, Long networkId, String keyword, List networkGurus); + + boolean isSecondaryIpSetForNic(long nicId); + + boolean removeVmSecondaryIpsOfNic(long nicId); + + NicVO savePlaceholderNic(Network network, String ip4Address, String ip6Address, Type vmType); + + NicVO savePlaceholderNic(Network network, String ip4Address, String ip6Address, String ip6Cidr, String ip6Gateway, String reserver, Type vmType); + + void unmanageNics(VirtualMachineProfile vm, BiConsumer removeNic); + + void expungeLbVmRefs(List networkElements, List vmIds, Long batchSize); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryServiceImpl.java new file mode 100644 index 000000000000..e1d6145d9974 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryServiceImpl.java @@ -0,0 +1,184 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.event.EventTypes; +import com.cloud.event.UsageEventUtils; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.network.guru.NetworkGuruAdditionalFunctions; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.vm.Nic; +import com.cloud.vm.Nic.ReservationStrategy; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicSecondaryIpDao; +import com.cloud.vm.dao.NicSecondaryIpVO; + +/** + * Auxiliary NIC lookups and placeholder persistence extracted from + * {@link NetworkOrchestrator}. + */ +@Component +public class NicAuxiliaryServiceImpl implements NicAuxiliaryService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NicDao nicDao; + + @Inject + protected NicSecondaryIpDao nicSecondaryIpDao; + + @Inject + protected NetworkDao networksDao; + + @Inject + protected NetworkModel networkModel; + + @Override + public List listVmNics(final long vmId, final Long nicId, final Long networkId, String keyword, List networkGurus) { + List result; + + if (keyword == null || keyword.isEmpty()) { + if (nicId == null && networkId == null) { + result = nicDao.listByVmId(vmId); + } else { + result = nicDao.listByVmIdAndNicIdAndNtwkId(vmId, nicId, networkId); + } + } else { + result = nicDao.listByVmIdAndKeyword(vmId, keyword); + } + + for (final NicVO nic : result) { + if (networkModel.isProviderForNetwork(Network.Provider.Nsx, nic.getNetworkId())) { + logger.info("Listing NSX logical switch and logical switch por for each nic"); + final NetworkVO network = networksDao.findById(nic.getNetworkId()); + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NetworkGuruAdditionalFunctions guruFunctions = (NetworkGuruAdditionalFunctions) guru; + + final Map nsxParams = guruFunctions.listAdditionalNicParams(nic.getUuid()); + if (nsxParams != null) { + final String lswitchUuuid = nsxParams.containsKey(NetworkGuruAdditionalFunctions.NSX_LSWITCH_UUID) + ? (String) nsxParams.get(NetworkGuruAdditionalFunctions.NSX_LSWITCH_UUID) : null; + final String lswitchPortUuuid = nsxParams.containsKey(NetworkGuruAdditionalFunctions.NSX_LSWITCHPORT_UUID) + ? (String) nsxParams.get(NetworkGuruAdditionalFunctions.NSX_LSWITCHPORT_UUID) : null; + nic.setNsxLogicalSwitchUuid(lswitchUuuid); + nic.setNsxLogicalSwitchPortUuid(lswitchPortUuuid); + } + } + } + + return result; + } + + @Override + public boolean isSecondaryIpSetForNic(final long nicId) { + final NicVO nic = nicDao.findById(nicId); + return nic.getSecondaryIp(); + } + + @Override + public boolean removeVmSecondaryIpsOfNic(final long nicId) { + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(final TransactionStatus status) { + final List ipList = nicSecondaryIpDao.listByNicId(nicId); + if (ipList != null) { + for (final NicSecondaryIpVO ip : ipList) { + nicSecondaryIpDao.remove(ip.getId()); + } + logger.debug("Revoving nic secondary ip entry ..."); + } + } + }); + + return true; + } + + @Override + public NicVO savePlaceholderNic(final Network network, final String ip4Address, final String ip6Address, final Type vmType) { + return savePlaceholderNic(network, ip4Address, ip6Address, null, null, null, vmType); + } + + @Override + public NicVO savePlaceholderNic(final Network network, final String ip4Address, final String ip6Address, final String ip6Cidr, final String ip6Gateway, final String reserver, final Type vmType) { + final NicVO nic = new NicVO(null, null, network.getId(), null); + nic.setIPv4Address(ip4Address); + nic.setIPv6Address(ip6Address); + nic.setIPv6Cidr(ip6Cidr); + nic.setIPv6Gateway(ip6Gateway); + nic.setReservationStrategy(ReservationStrategy.PlaceHolder); + if (reserver != null) { + nic.setReserver(reserver); + } + nic.setState(Nic.State.Reserved); + nic.setVmType(vmType); + return nicDao.persist(nic); + } + + @Override + public void unmanageNics(VirtualMachineProfile vm, BiConsumer removeNic) { + logger.debug("Unmanaging NICs for VM: {}", vm); + + VirtualMachine virtualMachine = vm.getVirtualMachine(); + final List nics = nicDao.listByVmId(vm.getId()); + for (final NicVO nic : nics) { + removeNic.accept(vm, nic); + NetworkVO network = networksDao.findById(nic.getNetworkId()); + if (virtualMachine.getState() != VirtualMachine.State.Stopped) { + UsageEventUtils.publishUsageEvent(EventTypes.EVENT_NETWORK_OFFERING_REMOVE, virtualMachine.getAccountId(), virtualMachine.getDataCenterId(), virtualMachine.getId(), + Long.toString(nic.getId()), network.getNetworkOfferingId(), null, 0L, virtualMachine.getClass().getName(), virtualMachine.getUuid(), virtualMachine.isDisplay()); + } + } + } + + @Override + public void expungeLbVmRefs(List networkElements, List vmIds, Long batchSize) { + if (CollectionUtils.isEmpty(networkElements) || CollectionUtils.isEmpty(vmIds)) { + return; + } + for (NetworkElement element : networkElements) { + if (element instanceof LoadBalancingServiceProvider) { + LoadBalancingServiceProvider lbProvider = (LoadBalancingServiceProvider)element; + lbProvider.expungeLbVmRefs(vmIds, batchSize); + } + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupService.java new file mode 100644 index 000000000000..72eed57ccde4 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupService.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.network.Network; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachineProfile; + +/** + * Handles DHCP/DNS entry cleanup for NICs during the NIC removal lifecycle. + * + *

Extracted from {@link NetworkOrchestrator} as part of the Phase 4 + * Spring-component decomposition. The orchestrator continues to expose the + * corresponding methods via one-line delegating wrappers so existing call + * sites and test spies keep working unchanged. + * + *

Responsibilities: + *

    + *
  • Remove DHCP entries from network elements when a NIC is released.
  • + *
  • Determine whether a DHCP provider supports multiple subnets.
  • + *
  • Detect whether a NIC is the last one in its subnet.
  • + *
  • Remove DHCP service from a subnet when the last NIC departs.
  • + *
+ */ +public interface NicDhcpCleanupService { + + /** + * Remove the DHCP and DNS entry for the given NIC/VM profile from + * all network elements that provide DHCP for the network. + * + *

Only removes entries for {@link com.cloud.vm.VirtualMachine.Type#User} + * VMs. Silently logs and swallows {@link com.cloud.exception.ResourceUnavailableException}. + * + * @param network the network the NIC belongs to + * @param vmProfile the VM profile being released + * @param nicProfile the NIC profile whose DHCP/DNS entry is to be removed + */ + void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile); + + /** + * Return {@code true} when the given {@link DhcpServiceProvider} advertises + * support for the {@code DhcpAccrossMultipleSubnets} capability. + * + * @param dhcpServiceProvider the DHCP provider to inspect + * @return {@code true} if multi-subnet DHCP is supported + */ + boolean isDhcpAccrossMultipleSubnetsSupported(DhcpServiceProvider dhcpServiceProvider); + + /** + * Return {@code true} when {@code nic} is the only {@link com.cloud.vm.VirtualMachine.Type#User} + * NIC in its subnet (identified by network-id, IPv4 gateway, and broadcast URI). + * + * @param nic the NIC to check + * @return {@code true} if no other user NIC shares the same subnet + */ + boolean isLastNicInSubnet(NicVO nic); + + /** + * Remove the DHCP service alias from the subnet that {@code nic} belongs + * to, releasing the IP alias and un-assigning the alias address. + * + *

Executes inside a database transaction. Logs and swallows + * {@link com.cloud.exception.ResourceUnavailableException} when the + * virtual router is unreachable. + * + * @param nic the NIC whose subnet DHCP service should be removed + */ + void removeDhcpServiceInSubnet(Nic nic); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupServiceImpl.java new file mode 100644 index 000000000000..821b506a7220 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupServiceImpl.java @@ -0,0 +1,159 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import com.cloud.utils.db.DB; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.Nic; +import com.cloud.vm.NicIpAlias; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicIpAliasDao; +import com.cloud.vm.dao.NicIpAliasVO; + +/** + * DHCP/DNS entry cleanup for NICs during the NIC removal lifecycle -- + * extracted from {@link NetworkOrchestrator}. + * + * @see NicDhcpCleanupService + */ +@Component +public class NicDhcpCleanupServiceImpl implements NicDhcpCleanupService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NetworkServiceMapDao networkServiceMapDao; + + @Inject + protected NetworkModel networkModel; + + @Inject + protected NetworkDao networksDao; + + @Inject + protected NicDao nicDao; + + @Inject + protected NicIpAliasDao nicIpAliasDao; + + @Inject + protected com.cloud.network.dao.IPAddressDao publicIpAddressDao; + + @Inject + protected NetworkProviderResolutionService networkProviderResolutionService; + + /** Injected by the orchestrator after construction. */ + protected List networkElements; + + @Override + public void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile) { + final List providerNames = networkServiceMapDao.getDistinctProviders(network.getId()); + final List networkProviders = new java.util.ArrayList<>(); + for (final String providerName : providerNames) { + networkProviders.add(Network.Provider.getProvider(providerName)); + } + + for (final NetworkElement element : networkElements) { + if (networkProviders.contains(element.getProvider())) { + if (!networkModel.isProviderEnabledInPhysicalNetwork(networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { + throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " + + network.getPhysicalNetworkId()); + } + if (vmProfile.getType() == VirtualMachine.Type.User && element.getProvider() != null) { + if (networkModel.areServicesSupportedInNetwork(network.getId(), Network.Service.Dhcp) + && networkModel.isProviderSupportServiceInNetwork(network.getId(), Network.Service.Dhcp, element.getProvider()) && element instanceof DhcpServiceProvider) { + final DhcpServiceProvider sp = (DhcpServiceProvider) element; + try { + sp.removeDhcpEntry(network, nicProfile, vmProfile); + } catch (ResourceUnavailableException e) { + logger.error("Failed to remove dhcp-dns entry due to: ", e); + } + } + } + } + } + } + + @Override + public boolean isDhcpAccrossMultipleSubnetsSupported(final DhcpServiceProvider dhcpServiceProvider) { + final Map capabilities = dhcpServiceProvider.getCapabilities().get(Network.Service.Dhcp); + final String supportsMultipleSubnets = capabilities.get(Network.Capability.DhcpAccrossMultipleSubnets); + if (supportsMultipleSubnets != null && Boolean.valueOf(supportsMultipleSubnets)) { + return true; + } + return false; + } + + @Override + public boolean isLastNicInSubnet(final NicVO nic) { + if (nicDao.listByNetworkIdTypeAndGatewayAndBroadcastUri(nic.getNetworkId(), VirtualMachine.Type.User, nic.getIPv4Gateway(), nic.getBroadcastUri()).size() > 1) { + return false; + } + return true; + } + + @DB + @Override + public void removeDhcpServiceInSubnet(final Nic nic) { + final Network network = networksDao.findById(nic.getNetworkId()); + final DhcpServiceProvider dhcpServiceProvider = networkProviderResolutionService.getDhcpServiceProvider(network); + try { + final NicIpAliasVO ipAlias = nicIpAliasDao.findByGatewayAndNetworkIdAndState(nic.getIPv4Gateway(), network.getId(), NicIpAlias.State.active); + if (ipAlias != null) { + ipAlias.setState(NicIpAlias.State.revoked); + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(final TransactionStatus status) { + nicIpAliasDao.update(ipAlias.getId(), ipAlias); + final IPAddressVO aliasIpaddressVo = publicIpAddressDao.findByIpAndSourceNetworkId(ipAlias.getNetworkId(), ipAlias.getIp4Address()); + publicIpAddressDao.unassignIpAddress(aliasIpaddressVo.getId()); + } + }); + if (!dhcpServiceProvider.removeDhcpSupportForSubnet(network)) { + logger.warn("Failed to remove the IP alias on the router, marking it as removed in db and freed the allocated IP {}", ipAlias.getIp4Address()); + } + } + } catch (final ResourceUnavailableException e) { + //failed to remove the dhcpconfig on the router. + logger.info("Unable to delete the IP alias due to unable to contact the virtualrouter."); + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationService.java new file mode 100644 index 000000000000..5d323fbd4cfe --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationService.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.element.NetworkElement; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachineProfile; + +public interface NicElementPreparationService { + + boolean prepareElement(NetworkElement element, Network network, NicProfile profile, VirtualMachineProfile vmProfile, DeployDestination dest, + ReservationContext context) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException; +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationServiceImpl.java new file mode 100644 index 000000000000..24e04dc85d00 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationServiceImpl.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.element.ConfigDriveNetworkElement; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.DnsServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.UserDataServiceProvider; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +@Component +public class NicElementPreparationServiceImpl implements NicElementPreparationService { + + @Inject + protected NetworkModel networkModel; + + @Inject + protected NicDhcpCleanupService nicDhcpCleanupService; + + @Override + public boolean prepareElement(final NetworkElement element, final Network network, final NicProfile profile, final VirtualMachineProfile vmProfile, + final DeployDestination dest, final ReservationContext context) + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + element.prepare(network, profile, vmProfile, dest, context); + if (vmProfile.getType() == VirtualMachine.Type.User && element.getProvider() != null) { + if (isServiceProvidedByElement(network, Service.Dhcp, element) && element instanceof DhcpServiceProvider) { + final DhcpServiceProvider sp = (DhcpServiceProvider) element; + if (nicDhcpCleanupService.isDhcpAccrossMultipleSubnetsSupported(sp)) { + if (!sp.configDhcpSupportForSubnet(network, profile, vmProfile, dest, context)) { + return false; + } + } + if (!sp.addDhcpEntry(network, profile, vmProfile, dest, context)) { + return false; + } + } + if (isServiceProvidedByElement(network, Service.Dns, element) && element instanceof DnsServiceProvider) { + final DnsServiceProvider sp = (DnsServiceProvider) element; + if (profile.getIPv6Address() == null) { + if (!sp.configDnsSupportForSubnet(network, profile, vmProfile, dest, context)) { + return false; + } + } + if (!sp.addDnsEntry(network, profile, vmProfile, dest, context)) { + return false; + } + } + if (isServiceProvidedByElement(network, Service.UserData, element) && element instanceof UserDataServiceProvider) { + final UserDataServiceProvider sp = (UserDataServiceProvider) element; + if (!sp.addPasswordAndUserdata(network, profile, vmProfile, dest, context)) { + return false; + } + } + if (element instanceof ConfigDriveNetworkElement && ( + isServiceProvidedByElement(network, Service.Dhcp, element) || + isServiceProvidedByElement(network, Service.Dns, element) || + isServiceProvidedByElement(network, Service.UserData, element))) { + final ConfigDriveNetworkElement sp = (ConfigDriveNetworkElement) element; + return sp.createConfigDriveIso(profile, vmProfile, dest, null); + } + } + return true; + } + + protected boolean isServiceProvidedByElement(final Network network, final Service service, final NetworkElement element) { + return networkModel.areServicesSupportedInNetwork(network.getId(), service) + && networkModel.isProviderSupportServiceInNetwork(network.getId(), service, element.getProvider()); + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicImportService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicImportService.java new file mode 100644 index 000000000000..7426caedeffe --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicImportService.java @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.dc.DataCenter; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.network.Network; +import com.cloud.utils.Pair; +import com.cloud.vm.NicProfile; +import com.cloud.vm.VirtualMachine; + +/** + * Handles import-NIC operations — extracted from {@link NetworkOrchestrator}. + * + * @see NicImportServiceImpl + */ +public interface NicImportService { + + Pair importNic(String macAddress, int deviceId, Network network, + Boolean isDefaultNic, VirtualMachine vm, Network.IpAddresses ipAddresses, + DataCenter dataCenter, boolean forced) + throws ConcurrentOperationException, + InsufficientVirtualNetworkCapacityException, + InsufficientAddressCapacityException; + + String getSelectedIpForNicImport(Network network, DataCenter dataCenter, + Network.IpAddresses ipAddresses); + + String getSelectedIpForNicImportOnSharedNetwork(String requestedIp, Network network, + DataCenter dataCenter); + + Pair getNetworkGatewayAndNetmaskForNicImport(Network network, + DataCenter dataCenter, String selectedIp); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicImportServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicImportServiceImpl.java new file mode 100644 index 000000000000..d74fd3d7b34e --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicImportServiceImpl.java @@ -0,0 +1,239 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.Date; +import java.util.UUID; + +import jakarta.inject.Inject; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.GuestType; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.user.Account; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.Nic; +import com.cloud.vm.Nic.ReservationStrategy; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.dao.NicDao; + +/** + * Handles import-NIC operations — extracted from {@link NetworkOrchestrator}. + * + * @see NicImportService + */ +@Component +public class NicImportServiceImpl implements NicImportService { + + protected Logger logger = LogManager.getLogger(NicImportServiceImpl.class); + + @Inject + protected NicDao nicDao; + @Inject + protected NetworkDao networksDao; + @Inject + protected IPAddressDao ipAddressDao; + @Inject + protected VlanDao vlanDao; + @Inject + protected NetworkModel networkModel; + @Inject + protected IpAddressManager ipAddrMgr; + @Inject + protected AccountDao accountDao; + @Inject + protected NicProfileMtuService nicProfileMtuService; + + @DB + @Override + public Pair importNic(final String macAddress, int deviceId, final Network network, final Boolean isDefaultNic, final VirtualMachine vm, + final Network.IpAddresses ipAddresses, final DataCenter dataCenter, final boolean forced) + throws ConcurrentOperationException, InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException { + logger.debug("Allocating NIC for Instance {} in Network {} during import", vm, network); + String selectedIp = null; + if (ipAddresses != null && StringUtils.isNotEmpty(ipAddresses.getIp4Address())) { + if (ipAddresses.getIp4Address().equals("auto")) { + ipAddresses.setIp4Address(null); + } + selectedIp = getSelectedIpForNicImport(network, dataCenter, ipAddresses); + if (selectedIp == null && network.getGuestType() != GuestType.L2 && !networkModel.listNetworkOfferingServices(network.getNetworkOfferingId()).isEmpty()) { + throw new InsufficientVirtualNetworkCapacityException("Unable to acquire Guest IP address for network " + network, DataCenter.class, + network.getDataCenterId()); + } + } + final String finalSelectedIp = selectedIp; + final NicVO vo = Transaction.execute(new TransactionCallback<>() { + @Override + public NicVO doInTransaction(TransactionStatus status) { + if (StringUtils.isBlank(macAddress)) { + throw new CloudRuntimeException("Mac address not specified"); + } + String macAddressToPersist = macAddress.trim(); + if (!NetUtils.isValidMac(macAddressToPersist)) { + throw new CloudRuntimeException("Invalid mac address: " + macAddressToPersist); + } + NicVO existingNic = nicDao.findByNetworkIdAndMacAddress(network.getId(), macAddressToPersist); + if (existingNic != null) { + macAddressToPersist = generateNewMacAddressIfForced(network, macAddressToPersist, forced); + } + NicVO vo = new NicVO(network.getGuruName(), vm.getId(), network.getId(), vm.getType()); + vo.setMacAddress(macAddressToPersist); + vo.setAddressFormat(Networks.AddressFormat.Ip4); + Pair pair = getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, finalSelectedIp); + String gateway = pair.first(); + String netmask = pair.second(); + if (NetUtils.isValidIp4(finalSelectedIp) && StringUtils.isNotEmpty(gateway)) { + vo.setIPv4Address(finalSelectedIp); + vo.setIPv4Gateway(gateway); + vo.setIPv4Netmask(netmask); + } + vo.setBroadcastUri(network.getBroadcastUri()); + vo.setMode(network.getMode()); + vo.setState(Nic.State.Reserved); + vo.setReservationStrategy(ReservationStrategy.Start); + vo.setReservationId(UUID.randomUUID().toString()); + vo.setIsolationUri(network.getBroadcastUri()); + vo.setDeviceId(deviceId); + vo.setDefaultNic(isDefaultNic); + vo = nicDao.persist(vo); + + int count = 1; + if (vo.getVmType() == VirtualMachine.Type.User) { + logger.debug("Changing active number of nics for network {} on {}", network, count); + networksDao.changeActiveNicsBy(network.getId(), count); + } + if (vo.getVmType() == VirtualMachine.Type.User + || vo.getVmType() == VirtualMachine.Type.DomainRouter && networksDao.findById(network.getId()).getTrafficType() == com.cloud.network.Networks.TrafficType.Guest) { + networksDao.setCheckForGc(network.getId()); + } + if (vm.getType() == Type.DomainRouter) { + Pair networks = nicProfileMtuService.getGuestNetworkRouterAndVpcDetails(vm.getId()); + nicProfileMtuService.setMtuDetailsInVRNic(networks, network, vo); + } + + return vo; + } + }); + + if (selectedIp != null && GuestType.Shared.equals(network.getGuestType())) { + IPAddressVO ipAddressVO = ipAddressDao.findByIpAndSourceNetworkId(network.getId(), selectedIp); + if (ipAddressVO != null && IpAddress.State.Free.equals(ipAddressVO.getState())) { + ipAddressVO.setState(IPAddressVO.State.Allocated); + ipAddressVO.setAllocatedTime(new Date()); + Account account = accountDao.findById(vm.getAccountId()); + ipAddressVO.setAllocatedInDomainId(account.getDomainId()); + ipAddressVO.setAllocatedToAccountId(account.getId()); + ipAddressDao.update(ipAddressVO.getId(), ipAddressVO); + } + } + + final Integer networkRate = networkModel.getNetworkRate(network.getId(), vm.getId()); + final NicProfile vmNic = new NicProfile(vo, network, vo.getBroadcastUri(), vo.getIsolationUri(), networkRate, networkModel.isSecurityGroupSupportedInNetwork(network), + networkModel.getNetworkTag(vm.getHypervisorType(), network)); + + return new Pair<>(vmNic, Integer.valueOf(deviceId)); + } + + @Override + public String getSelectedIpForNicImport(Network network, DataCenter dataCenter, Network.IpAddresses ipAddresses) { + if (network.getGuestType() == GuestType.L2) { + return null; + } + return GuestType.Shared.equals(network.getGuestType()) ? + getSelectedIpForNicImportOnSharedNetwork(ipAddresses.getIp4Address(), network, dataCenter) : + ipAddrMgr.acquireGuestIpAddress(network, ipAddresses.getIp4Address()); + } + + @Override + public String getSelectedIpForNicImportOnSharedNetwork(String requestedIp, Network network, DataCenter dataCenter) { + IPAddressVO ipAddressVO = StringUtils.isBlank(requestedIp) ? + ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(network.getId(), dataCenter.getId(), IpAddress.State.Free) : + ipAddressDao.findByIpAndSourceNetworkId(network.getId(), requestedIp); + if (ipAddressVO == null || ipAddressVO.getState() != IpAddress.State.Free) { + String msg = String.format("Cannot find a free IP to assign to VM NIC on network %s", network.getName()); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + return ipAddressVO.getAddress() != null ? ipAddressVO.getAddress().addr() : null; + } + + /** + * Obtain the gateway and netmask for a VM NIC to import. + * If the VM to import is on a Basic Zone, then obtain the information from the vlan table instead of the network. + */ + @Override + public Pair getNetworkGatewayAndNetmaskForNicImport(Network network, DataCenter dataCenter, String selectedIp) { + String gateway = network.getGateway(); + String netmask = StringUtils.isNotEmpty(network.getCidr()) ? NetUtils.cidr2Netmask(network.getCidr()) : null; + if (dataCenter.getNetworkType() == NetworkType.Basic) { + IPAddressVO freeIp = ipAddressDao.findByIp(selectedIp); + if (freeIp != null) { + VlanVO vlan = vlanDao.findById(freeIp.getVlanId()); + gateway = vlan != null ? vlan.getVlanGateway() : null; + netmask = vlan != null ? vlan.getVlanNetmask() : null; + } + } + return new Pair<>(gateway, netmask); + } + + private String generateNewMacAddressIfForced(Network network, String macAddress, boolean forced) { + if (!forced) { + throw new CloudRuntimeException("NIC with MAC address " + macAddress + " exists on network " + network + + " and forced flag is disabled"); + } + try { + logger.debug("Generating a new mac address on network {} as the mac address {} already exists", network, macAddress); + String newMacAddress = networkModel.getNextAvailableMacAddressInNetwork(network.getId()); + logger.debug("Successfully generated the mac address {}, using it instead of the conflicting address {}", newMacAddress, macAddress); + return newMacAddress; + } catch (InsufficientAddressCapacityException e) { + String msg = String.format("Could not generate a new mac address on network %s", network); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicMigrationService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicMigrationService.java new file mode 100644 index 000000000000..b34b1bfe3d4a --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicMigrationService.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.deploy.DeployDestination; +import com.cloud.vm.VirtualMachineProfile; + +public interface NicMigrationService { + + void prepareNicForMigration(VirtualMachineProfile vm, DeployDestination dest); + + void prepareAllNicsForMigration(VirtualMachineProfile vm, DeployDestination dest); + + void commitNicForMigration(VirtualMachineProfile src, VirtualMachineProfile dst); + + void rollbackNicForMigration(VirtualMachineProfile src, VirtualMachineProfile dst); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicMigrationServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicMigrationServiceImpl.java new file mode 100644 index 000000000000..6d713f1b747f --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicMigrationServiceImpl.java @@ -0,0 +1,306 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.dc.dao.VlanDao; +import com.cloud.deploy.DeployDestination; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Provider; +import com.cloud.network.NetworkMigrationResponder; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.ReservationContextImpl; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; + +@Component +public class NicMigrationServiceImpl implements NicMigrationService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NicDao nicDao; + @Inject + protected NetworkDao networksDao; + @Inject + protected NetworkModel networkModel; + @Inject + protected NetworkServiceMapDao networkServiceMapDao; + @Inject + protected IPAddressDao ipAddressDao; + @Inject + protected VlanDao vlanDao; + @Inject + protected PhysicalNetworkDao physicalNetworkDao; + @Inject + protected UserVmManager userVmManager; + + protected List networkGurus; + protected List networkElements; + + public void setNetworkGurus(final List networkGurus) { + this.networkGurus = networkGurus; + } + + public void setNetworkElements(final List networkElements) { + this.networkElements = networkElements; + } + + @Override + public void prepareNicForMigration(final VirtualMachineProfile vm, final DeployDestination dest) { + if (vm.getType().equals(VirtualMachine.Type.DomainRouter) + && (vm.getHypervisorType().equals(HypervisorType.KVM) || vm.getHypervisorType().equals(HypervisorType.VMware))) { + // Include nics hot plugged and not stored in DB. + prepareAllNicsForMigration(vm, dest); + return; + } + final List nics = nicDao.listByVmId(vm.getId()); + final ReservationContext context = new ReservationContextImpl(UUID.randomUUID().toString(), null, null); + for (final NicVO nic : nics) { + final NetworkVO network = networksDao.findById(nic.getNetworkId()); + final Integer networkRate = networkModel.getNetworkRate(network.getId(), vm.getId()); + + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, networkModel.isSecurityGroupSupportedInNetwork(network), + networkModel.getNetworkTag(vm.getHypervisorType(), network)); + if (guru instanceof NetworkMigrationResponder) { + if (!((NetworkMigrationResponder) guru).prepareMigration(profile, network, vm, dest, context)) { + logger.error("NetworkGuru {} prepareForMigration failed.", guru); // XXX: Transaction error + } + } + + if (network.getGuestType() == Network.GuestType.L2 && vm.getType() == VirtualMachine.Type.User) { + userVmManager.setupVmForPvlan(false, vm.getVirtualMachine().getHostId(), profile); + } + + final List providersToImplement = getNetworkProviders(network.getId()); + for (final NetworkElement element : networkElements) { + if (providersToImplement.contains(element.getProvider())) { + if (!networkModel.isProviderEnabledInPhysicalNetwork(networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { + throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " + + network.getPhysicalNetworkId()); + } + if (element instanceof NetworkMigrationResponder) { + if (!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)) { + logger.error("NetworkElement {} prepareForMigration failed.", element); // XXX: Transaction error + } + } + } + } + guru.updateNicProfile(profile, network); + vm.addNic(profile); + } + } + + /* + Prepare All Nics for migration including the nics dynamically created and not stored in DB + This is a temporary workaround work KVM migration + Once clean fix is added by stored dynamically nics is DB, this workaround won't be needed + */ + @Override + public void prepareAllNicsForMigration(final VirtualMachineProfile vm, final DeployDestination dest) { + final List nics = nicDao.listByVmId(vm.getId()); + final ReservationContext context = new ReservationContextImpl(UUID.randomUUID().toString(), null, null); + Long guestNetworkId = null; + for (final NicVO nic : nics) { + final NetworkVO network = networksDao.findById(nic.getNetworkId()); + if (network.getTrafficType().equals(TrafficType.Guest) && network.getGuestType().equals(GuestType.Isolated)) { + guestNetworkId = network.getId(); + } + final Integer networkRate = networkModel.getNetworkRate(network.getId(), vm.getId()); + + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, + networkModel.isSecurityGroupSupportedInNetwork(network), networkModel.getNetworkTag(vm.getHypervisorType(), network)); + if (guru instanceof NetworkMigrationResponder) { + if (!((NetworkMigrationResponder) guru).prepareMigration(profile, network, vm, dest, context)) { + logger.error("NetworkGuru {} prepareForMigration failed.", guru); // XXX: Transaction error + } + } + final List providersToImplement = getNetworkProviders(network.getId()); + for (final NetworkElement element : networkElements) { + if (providersToImplement.contains(element.getProvider())) { + if (!networkModel.isProviderEnabledInPhysicalNetwork(networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { + throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s", + element.getProvider().getName(), physicalNetworkDao.findById(network.getPhysicalNetworkId()))); + } + if (element instanceof NetworkMigrationResponder) { + if (!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)) { + logger.error("NetworkElement {} prepareForMigration failed.", element); // XXX: Transaction error + } + } + } + } + guru.updateNicProfile(profile, network); + vm.addNic(profile); + } + + final List addedURIs = new ArrayList<>(); + if (guestNetworkId != null) { + final List publicIps = ipAddressDao.listByAssociatedNetwork(guestNetworkId, null); + for (final IPAddressVO userIp : publicIps) { + final PublicIp publicIp = PublicIp.createFromAddrAndVlan(userIp, vlanDao.findById(userIp.getVlanId())); + final URI broadcastUri = BroadcastDomainType.Vlan.toUri(publicIp.getVlanTag()); + final long ntwkId = publicIp.getNetworkId(); + final Nic nic = nicDao.findByNetworkIdInstanceIdAndBroadcastUri(ntwkId, vm.getId(), broadcastUri.toString()); + if (nic == null && !addedURIs.contains(broadcastUri.toString())) { + // Nic details are not available in DB. Create nic profile for migration. + final NetworkVO network = networksDao.findById(ntwkId); + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NicProfile profile = new NicProfile(); + logger.debug("Creating NIC profile for migration. BroadcastUri: {} NetworkId: {} Instance: {}", broadcastUri.toString(), network, vm); + profile.setDeviceId(255); // dummyId + profile.setIPv4Address(userIp.getAddress().toString()); + profile.setIPv4Netmask(publicIp.getNetmask()); + profile.setIPv4Gateway(publicIp.getGateway()); + profile.setMacAddress(publicIp.getMacAddress()); + profile.setBroadcastType(network.getBroadcastDomainType()); + profile.setTrafficType(network.getTrafficType()); + profile.setBroadcastUri(broadcastUri); + profile.setIsolationUri(Networks.IsolationType.Vlan.toUri(publicIp.getVlanTag())); + profile.setSecurityGroupEnabled(networkModel.isSecurityGroupSupportedInNetwork(network)); + profile.setName(networkModel.getNetworkTag(vm.getHypervisorType(), network)); + profile.setNetworkRate(networkModel.getNetworkRate(network.getId(), vm.getId())); + profile.setNetworkId(network.getId()); + + guru.updateNicProfile(profile, network); + vm.addNic(profile); + addedURIs.add(broadcastUri.toString()); + } + } + } + } + + @Override + public void commitNicForMigration(final VirtualMachineProfile src, final VirtualMachineProfile dst) { + for (final NicProfile nicSrc : src.getNics()) { + final NetworkVO network = networksDao.findById(nicSrc.getNetworkId()); + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NicProfile nicDst = findNicProfileById(dst, nicSrc.getId()); + final ReservationContext src_context = new ReservationContextImpl(nicSrc.getReservationId(), null, null); + final ReservationContext dst_context = new ReservationContextImpl(nicDst.getReservationId(), null, null); + + if (guru instanceof NetworkMigrationResponder) { + ((NetworkMigrationResponder) guru).commitMigration(nicSrc, network, src, src_context, dst_context); + } + + if (network.getGuestType() == Network.GuestType.L2 && src.getType() == VirtualMachine.Type.User) { + userVmManager.setupVmForPvlan(true, src.getVirtualMachine().getHostId(), nicSrc); + } + + final List providersToImplement = getNetworkProviders(network.getId()); + for (final NetworkElement element : networkElements) { + if (providersToImplement.contains(element.getProvider())) { + if (!networkModel.isProviderEnabledInPhysicalNetwork(networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { + throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " + + network.getPhysicalNetworkId()); + } + if (element instanceof NetworkMigrationResponder) { + ((NetworkMigrationResponder) element).commitMigration(nicSrc, network, src, src_context, dst_context); + } + } + } + // update the reservation id + final NicVO nicVo = nicDao.findById(nicDst.getId()); + nicVo.setReservationId(nicDst.getReservationId()); + nicDao.persist(nicVo); + } + } + + @Override + public void rollbackNicForMigration(final VirtualMachineProfile src, final VirtualMachineProfile dst) { + for (final NicProfile nicDst : dst.getNics()) { + final NetworkVO network = networksDao.findById(nicDst.getNetworkId()); + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NicProfile nicSrc = findNicProfileById(src, nicDst.getId()); + final ReservationContext src_context = new ReservationContextImpl(nicSrc.getReservationId(), null, null); + final ReservationContext dst_context = new ReservationContextImpl(nicDst.getReservationId(), null, null); + + if (guru instanceof NetworkMigrationResponder) { + ((NetworkMigrationResponder) guru).rollbackMigration(nicDst, network, dst, src_context, dst_context); + } + + if (network.getGuestType() == Network.GuestType.L2 && src.getType() == VirtualMachine.Type.User) { + userVmManager.setupVmForPvlan(true, dst.getVirtualMachine().getHostId(), nicDst); + } + + final List providersToImplement = getNetworkProviders(network.getId()); + for (final NetworkElement element : networkElements) { + if (providersToImplement.contains(element.getProvider())) { + if (!networkModel.isProviderEnabledInPhysicalNetwork(networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { + throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " + + network.getPhysicalNetworkId()); + } + if (element instanceof NetworkMigrationResponder) { + ((NetworkMigrationResponder) element).rollbackMigration(nicDst, network, dst, src_context, dst_context); + } + } + } + } + } + + private NicProfile findNicProfileById(final VirtualMachineProfile vm, final long id) { + for (final NicProfile nic : vm.getNics()) { + if (nic.getId() == id) { + return nic; + } + } + return null; + } + + private List getNetworkProviders(final long networkId) { + final List providerNames = networkServiceMapDao.getDistinctProviders(networkId); + final List providers = new ArrayList<>(); + for (final String providerName : providerNames) { + providers.add(Network.Provider.getProvider(providerName)); + } + + return providers; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingService.java new file mode 100644 index 000000000000..bbd7c203420d --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingService.java @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; +import java.util.Map; + +import com.cloud.agent.api.to.NicTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.NetworkProfile; +import com.cloud.network.dao.NetworkVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; + +public interface NicProfileLifecycleMappingService { + + Integer applyProfileToNic(NicVO vo, NicProfile profile, Integer deviceId); + + void applyProfileToNicForRelease(NicVO vo, NicProfile profile); + + void applyProfileToNetwork(NetworkVO network, NetworkProfile profile); + + NicTO toNicTO(NicVO nic, NicProfile profile, NetworkVO config); + + NicProfile getNicProfileForVm(Network network, NicProfile requested, VirtualMachine vm); + + boolean getNicProfileDefaultNic(NicProfile nicProfile); + + List getNicProfiles(Long vmId, HypervisorType hypervisorType); + + List getNicProfiles(VirtualMachine vm); + + Map getSystemVMAccessDetails(VirtualMachine vm); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingServiceImpl.java new file mode 100644 index 000000000000..5e02db3f66e7 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingServiceImpl.java @@ -0,0 +1,256 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkProfile; +import com.cloud.network.Networks; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.utils.component.AdapterBase; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.NicDao; + +@Component +public class NicProfileLifecycleMappingServiceImpl implements NicProfileLifecycleMappingService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected NicDao nicDao; + @Inject + protected NetworkDao networksDao; + @Inject + protected NetworkModel networkModel; + + protected List networkGurus; + + public void setNetworkGurus(final List networkGurus) { + this.networkGurus = networkGurus; + } + + @Override + public Integer applyProfileToNic(final NicVO vo, final NicProfile profile, Integer deviceId) { + if (profile.getDeviceId() != null) { + vo.setDeviceId(profile.getDeviceId()); + } else if (deviceId != null) { + vo.setDeviceId(deviceId++); + } + + if (profile.getReservationStrategy() != null) { + vo.setReservationStrategy(profile.getReservationStrategy()); + } + + vo.setDefaultNic(profile.isDefaultNic()); + + vo.setIPv4Address(profile.getIPv4Address()); + vo.setAddressFormat(profile.getFormat()); + + if (profile.getMacAddress() != null) { + vo.setMacAddress(profile.getMacAddress()); + } + + vo.setMode(profile.getMode()); + vo.setIPv4Netmask(profile.getIPv4Netmask()); + vo.setIPv4Gateway(profile.getIPv4Gateway()); + + if (profile.getBroadCastUri() != null) { + vo.setBroadcastUri(profile.getBroadCastUri()); + } + + if (profile.getIsolationUri() != null) { + vo.setIsolationUri(profile.getIsolationUri()); + } + + vo.setState(Nic.State.Allocated); + + vo.setIPv6Address(profile.getIPv6Address()); + vo.setIPv6Gateway(profile.getIPv6Gateway()); + vo.setIPv6Cidr(profile.getIPv6Cidr()); + + return deviceId; + } + + @Override + public void applyProfileToNicForRelease(final NicVO vo, final NicProfile profile) { + vo.setIPv4Gateway(profile.getIPv4Gateway()); + vo.setAddressFormat(profile.getFormat()); + vo.setIPv4Address(profile.getIPv4Address()); + vo.setIPv6Address(profile.getIPv6Address()); + vo.setMacAddress(profile.getMacAddress()); + if (profile.getReservationStrategy() != null) { + vo.setReservationStrategy(profile.getReservationStrategy()); + } + vo.setBroadcastUri(profile.getBroadCastUri()); + vo.setIsolationUri(profile.getIsolationUri()); + vo.setIPv4Netmask(profile.getIPv4Netmask()); + } + + @Override + public void applyProfileToNetwork(final NetworkVO network, final NetworkProfile profile) { + network.setBroadcastUri(profile.getBroadcastUri()); + network.setDns1(profile.getDns1()); + network.setDns2(profile.getDns2()); + network.setPhysicalNetworkId(profile.getPhysicalNetworkId()); + } + + @Override + public NicTO toNicTO(final NicVO nic, final NicProfile profile, final NetworkVO config) { + final NicTO to = new NicTO(); + to.setDeviceId(nic.getDeviceId()); + to.setBroadcastType(config.getBroadcastDomainType()); + to.setType(config.getTrafficType()); + to.setIp(nic.getIPv4Address()); + to.setNetmask(nic.getIPv4Netmask()); + to.setMac(nic.getMacAddress()); + to.setDns1(profile.getIPv4Dns1()); + to.setDns2(profile.getIPv4Dns2()); + if (nic.getIPv4Gateway() != null) { + to.setGateway(nic.getIPv4Gateway()); + } else { + to.setGateway(config.getGateway()); + } + if (nic.getVmType() != VirtualMachine.Type.User) { + to.setPxeDisable(true); + } + to.setDefaultNic(nic.isDefaultNic()); + to.setBroadcastUri(nic.getBroadcastUri()); + to.setIsolationuri(nic.getIsolationUri()); + if (profile != null) { + to.setDns1(profile.getIPv4Dns1()); + to.setDns2(profile.getIPv4Dns2()); + } + + final Integer networkRate = networkModel.getNetworkRate(config.getId(), null); + to.setNetworkRateMbps(networkRate); + + to.setUuid(config.getUuid()); + + return to; + } + + @Override + public NicProfile getNicProfileForVm(final Network network, final NicProfile requested, final VirtualMachine vm) { + NicProfile nic = null; + if (requested != null && requested.getBroadCastUri() != null) { + final String broadcastUri = requested.getBroadCastUri().toString(); + final String ipAddress = requested.getIPv4Address(); + final NicVO nicVO = nicDao.findByNetworkIdInstanceIdAndBroadcastUri(network.getId(), vm.getId(), broadcastUri); + if (nicVO != null) { + if (ipAddress == null || nicVO.getIPv4Address().equals(ipAddress)) { + nic = networkModel.getNicProfile(vm, network.getId(), broadcastUri); + } + } + } else { + final NicVO nicVO = nicDao.findByNtwkIdAndInstanceId(network.getId(), vm.getId()); + if (nicVO != null) { + nic = networkModel.getNicProfile(vm, network.getId(), null); + } + } + return nic; + } + + @Override + public boolean getNicProfileDefaultNic(NicProfile nicProfile) { + if (nicProfile != null) { + logger.debug("Using requested nic profile isDefaultNic value [{}].", nicProfile.isDefaultNic()); + return nicProfile.isDefaultNic(); + } + + logger.debug("Using isDefaultNic default value [false] as requested nic profile is null."); + return false; + } + + @Override + public List getNicProfiles(final Long vmId, HypervisorType hypervisorType) { + final List nics = nicDao.listByVmId(vmId); + final List profiles = new ArrayList<>(); + + if (nics != null) { + for (final Nic nic : nics) { + final NetworkVO network = networksDao.findById(nic.getNetworkId()); + final Integer networkRate = networkModel.getNetworkRate(network.getId(), vmId); + + final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); + final NicProfile profile = new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), networkRate, + networkModel.isSecurityGroupSupportedInNetwork(network), networkModel.getNetworkTag(hypervisorType, network)); + guru.updateNicProfile(profile, network); + profiles.add(profile); + } + } + return profiles; + } + + @Override + public List getNicProfiles(final VirtualMachine vm) { + return getNicProfiles(vm.getId(), vm.getHypervisorType()); + } + + @Override + public Map getSystemVMAccessDetails(final VirtualMachine vm) { + final Map accessDetails = new HashMap<>(); + accessDetails.put(NetworkElementCommand.ROUTER_NAME, vm.getInstanceName()); + String privateIpAddress = null; + for (final NicProfile profile : getNicProfiles(vm)) { + if (profile == null) { + continue; + } + final Network network = networksDao.findById(profile.getNetworkId()); + if (network == null) { + continue; + } + final String address = profile.getIPv4Address(); + if (network.getTrafficType() == Networks.TrafficType.Control) { + accessDetails.put(NetworkElementCommand.ROUTER_IP, address); + } + if (network.getTrafficType() == Networks.TrafficType.Guest) { + accessDetails.put(NetworkElementCommand.ROUTER_GUEST_IP, address); + } + if (network.getTrafficType() == Networks.TrafficType.Management) { + privateIpAddress = address; + } + if (network.getTrafficType() != null && StringUtils.isNotEmpty(address)) { + accessDetails.put(network.getTrafficType().name(), address); + } + } + + if (privateIpAddress != null && StringUtils.isEmpty(accessDetails.get(NetworkElementCommand.ROUTER_IP))) { + accessDetails.put(NetworkElementCommand.ROUTER_IP, privateIpAddress); + } + return accessDetails; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuService.java new file mode 100644 index 000000000000..cc2cf5aed408 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuService.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.network.Network; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.utils.Pair; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; + +/** + * Resolves and applies MTU values to virtual-router NICs and NIC profiles + * based on the parent network and (optionally) the VPC that owns it. + * + *

Extracted from {@link NetworkOrchestrator} as part of the Phase 4 + * Spring-component decomposition. All extracted methods were package-private + * helpers with no direct test coverage in {@code NetworkOrchestratorTest}; + * the orchestrator now delegates to this service at the original call sites + * inside {@code allocateNic}, {@code prepareNic}, and {@code importNic}. + * + *

Responsibilities: + *

    + *
  • Look up the guest (or, falling back, public) network and VPC for a + * given virtual-router VM id.
  • + *
  • Apply the resolved MTU to a persistence-level {@link NicVO}.
  • + *
  • Apply the resolved MTU to a runtime {@link NicProfile}.
  • + *
+ */ +public interface NicProfileMtuService { + + /** + * Resolve the guest network (preferred) or public network and the owning + * VPC, if any, for the router VM with the supplied id. + * + *

The lookup first asks for a {@code Guest} traffic-type router row; if + * none exists it falls back to {@code Public}. When both fall through, the + * method returns {@code null}. + * + * @param routerId id of the domain router VM + * @return a pair of (network, vpc) -- {@code vpc} is {@code null} when the + * router is not bound to a VPC; the whole result is {@code null} + * when no router row is found + */ + Pair getGuestNetworkRouterAndVpcDetails(long routerId); + + /** + * Apply the appropriate MTU to a virtual-router {@link NicVO} based on + * the NIC's traffic type: + *

    + *
  • {@code Public} -- uses {@code VpcVO.getPublicMtu()} when the + * router is VPC-attached, otherwise {@code NetworkVO.getPublicMtu()}; + * does nothing when {@code networks} is {@code null}.
  • + *
  • {@code Guest} -- uses {@code network.getPrivateMtu()}.
  • + *
  • Any other traffic type -- no-op.
  • + *
+ * + * @param networks the (guest/public network, optional VPC) pair as returned + * by {@link #getGuestNetworkRouterAndVpcDetails(long)}; + * may be {@code null} + * @param network the network the NIC belongs to (drives the traffic-type + * branch) + * @param vo the NIC VO to mutate + */ + void setMtuDetailsInVRNic(Pair networks, Network network, NicVO vo); + + /** + * Apply the appropriate MTU to a runtime {@link NicProfile} based on the + * supplied traffic type: + *
    + *
  • {@code Public} -- uses {@code VpcVO.getPublicMtu()} when the + * router is VPC-attached, otherwise {@code NetworkVO.getPublicMtu()}.
  • + *
  • {@code Guest} -- uses {@code NetworkVO.getPrivateMtu()}.
  • + *
  • Any other traffic type -- no-op.
  • + *
+ * + *

No-op when {@code networks} or the first element of the pair is + * {@code null}. + * + * @param networks the (guest/public network, optional VPC) pair as + * returned by + * {@link #getGuestNetworkRouterAndVpcDetails(long)}; + * may be {@code null} + * @param trafficType the traffic type that drives the MTU choice + * @param vmNic the NIC profile to mutate + */ + void setMtuInVRNicProfile(Pair networks, TrafficType trafficType, NicProfile vmNic); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuServiceImpl.java new file mode 100644 index 000000000000..3fc7fd2bffd8 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuServiceImpl.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.network.Network; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; + +/** + * Resolves and applies virtual-router NIC MTU values -- extracted from + * {@link NetworkOrchestrator}. + * + * @see NicProfileMtuService + */ +@Component +public class NicProfileMtuServiceImpl implements NicProfileMtuService { + + @Inject + protected DomainRouterJoinDao routerJoinDao; + + @Inject + protected NetworkDao networksDao; + + @Inject + protected EntityManager entityManager; + + @Override + public Pair getGuestNetworkRouterAndVpcDetails(long routerId) { + List routerVo = routerJoinDao.getRouterByIdAndTrafficType(routerId, TrafficType.Guest); + if (routerVo.isEmpty()) { + routerVo = routerJoinDao.getRouterByIdAndTrafficType(routerId, TrafficType.Public); + if (routerVo.isEmpty()) { + return null; + } + } + DomainRouterJoinVO guestRouterDetails = routerVo.get(0); + VpcVO vpc = null; + if (guestRouterDetails.getVpcId() != 0) { + vpc = entityManager.findById(VpcVO.class, guestRouterDetails.getVpcId()); + } + long networkId = guestRouterDetails.getNetworkId(); + return new Pair<>(networksDao.findById(networkId), vpc); + } + + @Override + public void setMtuDetailsInVRNic(final Pair networks, Network network, NicVO vo) { + if (TrafficType.Public == network.getTrafficType()) { + if (networks == null) { + return; + } + NetworkVO networkVO = networks.first(); + VpcVO vpcVO = networks.second(); + if (vpcVO != null) { + vo.setMtu(vpcVO.getPublicMtu()); + } else { + vo.setMtu(networkVO.getPublicMtu()); + } + } else if (TrafficType.Guest == network.getTrafficType()) { + vo.setMtu(network.getPrivateMtu()); + } + } + + @Override + public void setMtuInVRNicProfile(final Pair networks, TrafficType trafficType, NicProfile vmNic) { + if (networks == null) { + return; + } + NetworkVO networkVO = networks.first(); + VpcVO vpcVO = networks.second(); + if (networkVO != null) { + if (TrafficType.Public == trafficType) { + if (vpcVO != null) { + vmNic.setMtu(vpcVO.getPublicMtu()); + } else { + vmNic.setMtu(networkVO.getPublicMtu()); + } + } else if (TrafficType.Guest == trafficType) { + vmNic.setMtu(networkVO.getPrivateMtu()); + } + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupService.java new file mode 100644 index 000000000000..b91ab85bc9b5 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupService.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offerings.NetworkOfferingVO; + +public interface PersistentNetworkSetupService { + void setupPersistentNetwork(NetworkVO network, NetworkOfferingVO offering, Long dcId) throws AgentUnavailableException, OperationTimedoutException; +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupServiceImpl.java new file mode 100644 index 000000000000..ef5816d1c1eb --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupServiceImpl.java @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.SetupPersistentNetworkAnswer; +import com.cloud.agent.api.SetupPersistentNetworkCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.resource.ResourceManager; +import com.cloud.utils.Pair; + +@Component +public class PersistentNetworkSetupServiceImpl implements PersistentNetworkSetupService { + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected ClusterDao clusterDao; + @Inject + protected ResourceManager resourceManager; + @Inject + protected NetworkModel networkModel; + @Inject + protected ConfigurationManager configurationManager; + @Inject + protected AgentManager agentManager; + + @Override + public void setupPersistentNetwork(final NetworkVO network, final NetworkOfferingVO offering, final Long dcId) throws AgentUnavailableException, OperationTimedoutException { + final List clusterVOs = clusterDao.listClustersByDcId(dcId); + final List hosts = resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, dcId); + final Map> clusterToHostsMap = new HashMap<>(); + + for (final HostVO host : hosts) { + try { + final Pair networkCfgStateAndDetails = isNtwConfiguredInCluster(host, clusterToHostsMap, network, offering); + if (networkCfgStateAndDetails.first()) { + continue; + } + final NicTO to = networkCfgStateAndDetails.second(); + final SetupPersistentNetworkCommand cmd = new SetupPersistentNetworkCommand(to); + final SetupPersistentNetworkAnswer answer = (SetupPersistentNetworkAnswer) agentManager.send(host.getId(), cmd); + + if (answer == null) { + logger.warn("Unable to get an answer to the SetupPersistentNetworkCommand from agent: {}", host); + clusterToHostsMap.get(host.getClusterId()).remove(host.getId()); + continue; + } + + if (!answer.getResult()) { + logger.warn("Unable to setup agent {} due to {}", host, answer.getDetails()); + clusterToHostsMap.get(host.getClusterId()).remove(host.getId()); + } + } catch (final Exception e) { + logger.warn("Failed to connect to host: {}", host); + } + } + if (clusterToHostsMap.keySet().size() != clusterVOs.size()) { + logger.warn("Hosts on all clusters may not have been configured with network devices."); + } + } + + private Pair isNtwConfiguredInCluster(final HostVO host, final Map> clusterToHostsMap, final NetworkVO network, + final NetworkOfferingVO offering) { + final Long clusterId = host.getClusterId(); + List hosts = clusterToHostsMap.get(clusterId); + if (hosts == null) { + hosts = new ArrayList<>(); + } + if (host.getHypervisorType() == HypervisorType.KVM || host.getHypervisorType() == HypervisorType.XenServer) { + hosts.add(host.getId()); + clusterToHostsMap.put(clusterId, hosts); + return new Pair<>(false, createNicTOFromNetworkAndOffering(network, offering, host)); + } + if (hosts != null && !hosts.isEmpty()) { + return new Pair<>(true, createNicTOFromNetworkAndOffering(network, offering, host)); + } + hosts.add(host.getId()); + clusterToHostsMap.put(clusterId, hosts); + return new Pair<>(false, createNicTOFromNetworkAndOffering(network, offering, host)); + } + + private NicTO createNicTOFromNetworkAndOffering(final NetworkVO network, final NetworkOfferingVO offering, final HostVO host) { + final NicTO to = new NicTO(); + to.setName(networkModel.getNetworkTag(host.getHypervisorType(), network)); + to.setBroadcastType(network.getBroadcastDomainType()); + to.setType(network.getTrafficType()); + to.setBroadcastUri(network.getBroadcastUri()); + to.setIsolationuri(network.getBroadcastUri()); + to.setNetworkRateMbps(configurationManager.getNetworkOfferingNetworkRate(offering.getId(), network.getDataCenterId())); + to.setSecurityGroupEnabled(networkModel.isSecurityGroupSupportedInNetwork(network)); + return to; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationService.java new file mode 100644 index 000000000000..b26fe5dadbd5 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationService.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.network.Network; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.vm.NicProfile; + +public interface RequestedNicIpReservationService { + + void configureNicProfileBasedOnRequestedIp(NicProfile requestedNicProfile, NicProfile nicProfile, Network network); + + void acquireLockAndCheckIfIpv4IsFree(Network network, String requestedIpv4Address); + + void validateLockedRequestedIp(IPAddressVO ipVO, IPAddressVO lockedIpVO); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationServiceImpl.java new file mode 100644 index 000000000000..4407bdca2a21 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationServiceImpl.java @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.Date; + +import jakarta.inject.Inject; + +import org.springframework.stereotype.Component; + +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.NicProfile; + +@Component +public class RequestedNicIpReservationServiceImpl implements RequestedNicIpReservationService { + + @Inject + protected VlanDao vlanDao; + @Inject + protected IPAddressDao ipAddressDao; + @Inject + protected NetworkModel networkModel; + + /** + * If the requested IPv4 address from the NicProfile was configured then it configures the IPv4 address, Netmask and Gateway to deploy the VM with the requested IP. + */ + @Override + public void configureNicProfileBasedOnRequestedIp(NicProfile requestedNicProfile, NicProfile nicProfile, Network network) { + if (requestedNicProfile == null) { + return; + } + String requestedIpv4Address = requestedNicProfile.getRequestedIPv4(); + if (requestedIpv4Address == null) { + return; + } + if (!NetUtils.isValidIp4(requestedIpv4Address)) { + throw new InvalidParameterValueException(String.format("The requested [IPv4 address='%s'] is not a valid IP address", requestedIpv4Address)); + } + + VlanVO vlanVo = vlanDao.findByNetworkIdAndIpv4(network.getId(), requestedIpv4Address); + if (vlanVo == null) { + throw new InvalidParameterValueException(String.format("Trying to configure a Nic with the requested [IPv4='%s'] but cannot find a Vlan for the [network '%s']", + requestedIpv4Address, network)); + } + + String ipv4Gateway = vlanVo.getVlanGateway(); + String ipv4Netmask = vlanVo.getVlanNetmask(); + + if (!NetUtils.isValidIp4(ipv4Gateway)) { + throw new InvalidParameterValueException(String.format("The [IPv4Gateway='%s'] from [Vlan id=%d uuid=%s] is not valid", ipv4Gateway, vlanVo.getId(), vlanVo.getUuid())); + } + if (!NetUtils.isValidIp4Netmask(ipv4Netmask)) { + throw new InvalidParameterValueException(String.format("The [IPv4Netmask='%s'] from [Vlan id=%d uuid=%s] is not valid", ipv4Netmask, vlanVo.getId(), vlanVo.getUuid())); + } + + acquireLockAndCheckIfIpv4IsFree(network, requestedIpv4Address); + + nicProfile.setIPv4Address(requestedIpv4Address); + nicProfile.setIPv4Gateway(ipv4Gateway); + nicProfile.setIPv4Netmask(ipv4Netmask); + + if (nicProfile.getMacAddress() == null || !networkModel.isMACUnique(nicProfile.getMacAddress(), network.getId())) { + try { + String macAddress = networkModel.getNextAvailableMacAddressInNetwork(network.getId()); + nicProfile.setMacAddress(macAddress); + } catch (InsufficientAddressCapacityException e) { + throw new CloudRuntimeException(String.format("Cannot get next available mac address in [network %s]", network), e); + } + } + } + + /** + * Acquires lock in "user_ip_address" and checks if the requested IPv4 address is Free. + */ + @Override + public void acquireLockAndCheckIfIpv4IsFree(Network network, String requestedIpv4Address) { + IPAddressVO ipVO = ipAddressDao.findByIpAndSourceNetworkId(network.getId(), requestedIpv4Address); + if (ipVO == null) { + throw new InvalidParameterValueException( + String.format("Cannot find IPAddressVO for guest [IPv4 address='%s'] and [network %s]", requestedIpv4Address, network)); + } + try { + IPAddressVO lockedIpVO = ipAddressDao.acquireInLockTable(ipVO.getId()); + validateLockedRequestedIp(ipVO, lockedIpVO); + lockedIpVO.setState(IPAddressVO.State.Allocated); + lockedIpVO.setAllocatedTime(new Date()); + ipAddressDao.update(lockedIpVO.getId(), lockedIpVO); + } finally { + ipAddressDao.releaseFromLockTable(ipVO.getId()); + } + } + + /** + * Validates the locked IP, throwing an exception if the locked IP is null or the locked IP is not in 'Free' state. + */ + @Override + public void validateLockedRequestedIp(IPAddressVO ipVO, IPAddressVO lockedIpVO) { + if (lockedIpVO == null) { + throw new InvalidParameterValueException(String.format("Cannot acquire guest [IPv4 address='%s'] as it was removed while acquiring lock", ipVO.getAddress())); + } + if (lockedIpVO.getState() != IPAddressVO.State.Free) { + throw new InvalidParameterValueException( + String.format("Cannot acquire guest [IPv4 address='%s']; The Ip address is in [state='%s']", ipVO.getAddress(), lockedIpVO.getState().toString())); + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateService.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateService.java new file mode 100644 index 000000000000..2bd8e79f3317 --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateService.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import com.cloud.vm.NicProfile; +import com.cloud.vm.VirtualMachineProfile; + +public interface RouterDefaultDnsUpdateService { + + void updateRouterDefaultDns(VirtualMachineProfile vmProfile, NicProfile nicProfile); +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateServiceImpl.java new file mode 100644 index 000000000000..1062a023b3fd --- /dev/null +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateServiceImpl.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import java.util.List; + +import jakarta.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.RouterNetworkDao; +import com.cloud.network.vpc.Vpc; +import com.cloud.network.vpc.VpcManager; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.DomainRouterDao; + +@Component +public class RouterDefaultDnsUpdateServiceImpl implements RouterDefaultDnsUpdateService { + + @Inject + protected DomainRouterDao routerDao; + @Inject + protected RouterNetworkDao routerNetworkDao; + @Inject + protected VpcManager vpcManager; + @Inject + protected NetworkDao networksDao; + + @Override + public void updateRouterDefaultDns(final VirtualMachineProfile vmProfile, final NicProfile nicProfile) { + if (!Type.DomainRouter.equals(vmProfile.getType()) || !nicProfile.isDefaultNic()) { + return; + } + DomainRouterVO router = routerDao.findById(vmProfile.getId()); + if (router != null && router.getVpcId() != null) { + final Vpc vpc = vpcManager.getActiveVpc(router.getVpcId()); + if (StringUtils.isNotBlank(vpc.getIp4Dns1())) { + nicProfile.setIPv4Dns1(vpc.getIp4Dns1()); + nicProfile.setIPv4Dns2(vpc.getIp4Dns2()); + } + if (StringUtils.isNotBlank(vpc.getIp6Dns1())) { + nicProfile.setIPv6Dns1(vpc.getIp6Dns1()); + nicProfile.setIPv6Dns2(vpc.getIp6Dns2()); + } + return; + } + List networkIds = routerNetworkDao.getRouterNetworks(vmProfile.getId()); + if (CollectionUtils.isEmpty(networkIds) || networkIds.size() > 1) { + return; + } + final NetworkVO routerNetwork = networksDao.findById(networkIds.get(0)); + if (StringUtils.isNotBlank(routerNetwork.getDns1())) { + nicProfile.setIPv4Dns1(routerNetwork.getDns1()); + nicProfile.setIPv4Dns2(routerNetwork.getDns2()); + } + if (StringUtils.isNotBlank(routerNetwork.getIp6Dns1())) { + nicProfile.setIPv6Dns1(routerNetwork.getIp6Dns1()); + nicProfile.setIPv6Dns2(routerNetwork.getIp6Dns2()); + } + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java index 933b4e0c5ce6..2a203f3fb155 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java @@ -33,7 +33,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.dc.dao.DataCenterDao; diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index bf3985d3ce77..84de181d6978 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -35,7 +35,7 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.deploy.DeploymentClusterPlanner; @@ -1156,8 +1156,6 @@ private ImageFormat getSupportedImageFormatForCluster(HypervisorType hyperType) return ImageFormat.QCOW2; } else if (hyperType == HypervisorType.VMware) { return ImageFormat.OVA; - } else if (hyperType == HypervisorType.Ovm) { - return ImageFormat.RAW; } else if (hyperType == HypervisorType.Hyperv) { return ImageFormat.VHDX; } else { diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java index ff75aa0cbb65..3902f68199e1 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java @@ -22,8 +22,8 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; -import javax.ws.rs.Path; +import jakarta.inject.Inject; +import jakarta.ws.rs.Path; import org.apache.cloudstack.engine.datacenter.entity.api.ClusterEntity; import org.apache.cloudstack.engine.datacenter.entity.api.ClusterEntityImpl; diff --git a/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml b/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml index 17c5002c718b..f5d22b5a3e67 100644 --- a/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml +++ b/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml @@ -63,6 +63,20 @@ + + + + + + + + + + + diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index a07870d09af2..7af1fd9f4ec4 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -39,15 +40,13 @@ import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.Random; import java.util.UUID; -import java.util.stream.Collectors; import com.cloud.agent.api.UnmanageInstanceAnswer; import com.cloud.agent.api.UnmanageInstanceCommand; @@ -60,8 +59,11 @@ import com.cloud.network.Network; import com.cloud.network.NetworkModel; import com.cloud.resource.ResourceManager; +import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; @@ -72,17 +74,16 @@ import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO; import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.gpu.GpuService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; -import org.apache.commons.collections.MapUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedStatic; @@ -102,26 +103,17 @@ import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.api.query.dao.UserVmJoinDao; -import com.cloud.api.query.vo.UserVmJoinVO; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.ClusterDetailsVO; -import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; -import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; -import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.DeploymentPlanningManager; -import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; import com.cloud.exception.AgentUnavailableException; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -130,21 +122,17 @@ import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.network.NetworkService; import com.cloud.network.dao.NetworkDao; -import com.cloud.network.dao.NetworkVO; -import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.offering.DiskOfferingInfo; import com.cloud.offering.ServiceOffering; import com.cloud.org.Cluster; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; -import com.cloud.storage.ScopeType; -import com.cloud.storage.Storage; +import com.cloud.storage.Snapshot; import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; -import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.VMTemplateVO; -import com.cloud.storage.VMTemplateZoneVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.DiskOfferingDao; @@ -156,6 +144,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountVO; +import com.cloud.user.ResourceLimitService; import com.cloud.user.User; import com.cloud.user.dao.AccountDao; import com.cloud.utils.Journal; @@ -169,6 +158,8 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotManager; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; @RunWith(MockitoJUnitRunner.class) public class VirtualMachineManagerImplTest { @@ -190,6 +181,14 @@ public class VirtualMachineManagerImplTest { private VMInstanceVO vmInstanceMock; @Mock private VmWorkJobDao _workJobDao; + @Mock + private VmWorkJobQueueService vmWorkJobQueueService; + @Mock + private NetworkOrchestrationService networkMgr; + @Mock + private VolumeOrchestrationService volumeMgr; + @Mock + private ResourceLimitService resourceLimitMgr; private long vmInstanceVoMockId = 1L; @@ -241,8 +240,6 @@ public class VirtualMachineManagerImplTest { @Mock private HostDao hostDaoMock; @Mock - private UserVmJoinDao userVmJoinDaoMock; - @Mock private UserVmDao userVmDaoMock; @Mock private UserVmVO userVmMock; @@ -267,8 +264,6 @@ public class VirtualMachineManagerImplTest { @Mock private ClusterDao clusterDao; @Mock - private ClusterDetailsDao _clusterDetailsDao; - @Mock private VMInstanceDetailsDao vmInstanceDetailsDao; @Mock private ItWorkDao _workDao; @@ -292,6 +287,48 @@ public class VirtualMachineManagerImplTest { private HighAvailabilityManager _haMgr; @Mock VirtualMachineGuru guru; + @Mock + private VmOfflineStorageMigrationService vmOfflineStorageMigrationService; + @Mock + private VmOfflineStorageMigrationServiceImpl vmOfflineStorageMigrationServiceImpl; + @Mock + private VmVolumeMigrationPlanningService vmVolumeMigrationPlanningService; + @Mock + private VmVolumeMigrationPlanningServiceImpl vmVolumeMigrationPlanningServiceImpl; + @Mock + private VmDiskOfferingSuitabilityService vmDiskOfferingSuitabilityService; + @Mock + private VmCommandSpecPostProcessingService vmCommandSpecPostProcessingService; + @Mock + private VmMetadataSyncService vmMetadataSyncService; + @Mock + private VmNetworkNameMappingService vmNetworkNameMappingService; + @Mock + private VmStartProfilePreparationService vmStartProfilePreparationService; + @Mock + private VmVlanPersistenceMappingService vmVlanPersistenceMappingService; + @Mock + private VmMigrationCheckpointService vmMigrationCheckpointService; + @Mock + private VmAllocationOrchestrationService vmAllocationOrchestrationService; + @Mock + private VMSnapshotDao vmSnapshotDao; + @Mock + private VMSnapshotManager vmSnapshotManager; + @Mock + private GpuService gpuService; + @Mock + private BackupManager backupManager; + @Mock + private VmNicBackendCommandService vmNicBackendCommandService; + @Mock + private VmMigrateAwayPlanningService vmMigrateAwayPlanningService; + @Mock + private VmScaleReconfigurationService vmScaleReconfigurationService; + @Mock + private VmExpungeOrchestrationService vmExpungeOrchestrationService; + @Mock + private VmNetworkAttachmentOrchestrationService vmNetworkAttachmentOrchestrationService; private ConfigDepotImpl configDepotImpl; private boolean updatedConfigKeyDepot = false; @@ -303,30 +340,106 @@ public void setup() { when(vmInstanceMock.getName()).thenReturn(vmName); when(vmInstanceMock.getId()).thenReturn(vmInstanceVoMockId); - when(vmInstanceMock.getServiceOfferingId()).thenReturn(2L); - when(hostMock.getId()).thenReturn(hostMockId); - when(dataCenterDeploymentMock.getHostId()).thenReturn(hostMockId); - when(dataCenterDeploymentMock.getClusterId()).thenReturn(clusterMockId); - when(hostMock.getDataCenterId()).thenReturn(zoneMockId); when(hostDaoMock.findById(any())).thenReturn(hostMock); - when(userVmJoinDaoMock.searchByIds(any())).thenReturn(new ArrayList<>()); when(userVmDaoMock.findById(any())).thenReturn(userVmMock); - Mockito.doReturn(vmInstanceVoMockId).when(virtualMachineProfileMock).getId(); - - Mockito.doReturn(storagePoolVoMockId).when(storagePoolVoMock).getId(); - Mockito.doReturn(volumeMockId).when(volumeVoMock).getId(); - Mockito.doReturn(storagePoolVoMockId).when(volumeVoMock).getPoolId(); - - Mockito.doReturn(volumeVoMock).when(volumeDaoMock).findById(volumeMockId); - Mockito.doReturn(storagePoolVoMock).when(storagePoolDaoMock).findById(storagePoolVoMockId); ArrayList storagePoolAllocators = new ArrayList<>(); storagePoolAllocators.add(storagePoolAllocatorMock); virtualMachineManagerImpl.setStoragePoolAllocators(storagePoolAllocators); + + // Wire a real VmServiceOfferingUpgradeManager backed by the same + // DAO/manager mocks so the delegating wrappers in + // VirtualMachineManagerImpl behave like the original inline + // implementations. + VmServiceOfferingUpgradeManagerImpl upgradeManager = new VmServiceOfferingUpgradeManagerImpl(); + ReflectionTestUtils.setField(upgradeManager, "volumeDao", volumeDaoMock); + ReflectionTestUtils.setField(upgradeManager, "storagePoolDao", storagePoolDaoMock); + ReflectionTestUtils.setField(upgradeManager, "vmInstanceDao", vmInstanceDaoMock); + ReflectionTestUtils.setField(upgradeManager, "vmInstanceDetailsDao", vmInstanceDetailsDao); + ReflectionTestUtils.setField(upgradeManager, "templateDao", templateDao); + ReflectionTestUtils.setField(upgradeManager, "serviceOfferingDao", serviceOfferingDaoMock); + ReflectionTestUtils.setField(upgradeManager, "diskOfferingDao", diskOfferingDaoMock); + ReflectionTestUtils.setField(upgradeManager, "entityMgr", _entityMgr); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmServiceOfferingUpgradeManager", upgradeManager); + + // Wire a real VmIscsiTargetManager backed by the same DAO/agent + // mocks so the unmanage flow behaves like the original inline + // implementation. + VmIscsiTargetManagerImpl iscsiTargetManager = new VmIscsiTargetManagerImpl(); + ReflectionTestUtils.setField(iscsiTargetManager, "hostDao", hostDaoMock); + ReflectionTestUtils.setField(iscsiTargetManager, "volumeDao", volumeDaoMock); + ReflectionTestUtils.setField(iscsiTargetManager, "storagePoolDao", storagePoolDaoMock); + ReflectionTestUtils.setField(iscsiTargetManager, "agentMgr", agentManagerMock); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmIscsiTargetManager", iscsiTargetManager); + + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmExpungeOrchestrationService", vmExpungeOrchestrationService); + + VmDestroyOrchestrationServiceImpl destroyOrchestrationService = new VmDestroyOrchestrationServiceImpl(); + ReflectionTestUtils.setField(destroyOrchestrationService, "vmDao", vmInstanceDaoMock); + ReflectionTestUtils.setField(destroyOrchestrationService, "userVmDao", userVmDaoMock); + ReflectionTestUtils.setField(destroyOrchestrationService, "vmSnapshotDao", vmSnapshotDao); + ReflectionTestUtils.setField(destroyOrchestrationService, "vmSnapshotMgr", vmSnapshotManager); + ReflectionTestUtils.setField(destroyOrchestrationService, "agentMgr", agentManagerMock); + ReflectionTestUtils.setField(destroyOrchestrationService, "gpuService", gpuService); + ReflectionTestUtils.setField(destroyOrchestrationService, "backupManager", backupManager); + ReflectionTestUtils.setField(destroyOrchestrationService, "virtualMachineManager", virtualMachineManagerImpl); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmDestroyOrchestrationService", destroyOrchestrationService); + + // Wire a real VmExternalProvisioningManager backed by the same + // DAO/manager mocks so the delegating wrappers in + // VirtualMachineManagerImpl exercise the extracted behaviour. + VmExternalProvisioningManagerImpl externalProvisioningManager = new VmExternalProvisioningManagerImpl(); + ReflectionTestUtils.setField(externalProvisioningManager, "agentMgr", agentManagerMock); + ReflectionTestUtils.setField(externalProvisioningManager, "nicsDao", _nicsDao); + ReflectionTestUtils.setField(externalProvisioningManager, "userVmDao", userVmDaoMock); + ReflectionTestUtils.setField(externalProvisioningManager, "extensionsManager", extensionsManager); + ReflectionTestUtils.setField(externalProvisioningManager, "extensionDetailsDao", extensionDetailsDao); + ReflectionTestUtils.setField(externalProvisioningManager, "networkService", networkService); + ReflectionTestUtils.setField(externalProvisioningManager, "hostDao", hostDaoMock); + ReflectionTestUtils.setField(externalProvisioningManager, "networkModel", networkModel); + ReflectionTestUtils.setField(externalProvisioningManager, "hvGuruMgr", _hvGuruMgr); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmExternalProvisioningManager", externalProvisioningManager); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmOfflineStorageMigrationService", vmOfflineStorageMigrationService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmOfflineStorageMigrationServiceImpl", vmOfflineStorageMigrationServiceImpl); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmVolumeMigrationPlanningService", vmVolumeMigrationPlanningService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmVolumeMigrationPlanningServiceImpl", vmVolumeMigrationPlanningServiceImpl); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmDiskOfferingSuitabilityService", vmDiskOfferingSuitabilityService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmNetworkNameMappingService", vmNetworkNameMappingService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmStartProfilePreparationService", vmStartProfilePreparationService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmVlanPersistenceMappingService", vmVlanPersistenceMappingService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmNicBackendCommandService", vmNicBackendCommandService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmScaleReconfigurationService", vmScaleReconfigurationService); + VmStopCommandServiceImpl vmStopCommandService = new VmStopCommandServiceImpl(); + ReflectionTestUtils.setField(vmStopCommandService, "nicsDao", _nicsDao); + ReflectionTestUtils.setField(vmStopCommandService, "vmDao", vmInstanceDaoMock); + ReflectionTestUtils.setField(vmStopCommandService, "vmVlanPersistenceMappingService", vmVlanPersistenceMappingService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmStopCommandService", vmStopCommandService); + VmStopOrchestrationServiceImpl vmStopOrchestrationService = new VmStopOrchestrationServiceImpl(); + ReflectionTestUtils.setField(vmStopOrchestrationService, "agentMgr", agentManagerMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "vmDao", vmInstanceDaoMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "hostDao", hostDaoMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "workDao", _workDao); + ReflectionTestUtils.setField(vmStopOrchestrationService, "userVmDao", userVmDaoMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "volsDao", volumeDaoMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "storagePoolDao", storagePoolDaoMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "offeringDao", serviceOfferingDaoMock); + ReflectionTestUtils.setField(vmStopOrchestrationService, "templateDao", templateDao); + ReflectionTestUtils.setField(vmStopOrchestrationService, "resourceMgr", _resourceMgr); + ReflectionTestUtils.setField(vmStopOrchestrationService, "resourceLimitMgr", resourceLimitMgr); + ReflectionTestUtils.setField(vmStopOrchestrationService, "volumeMgr", volumeMgr); + ReflectionTestUtils.setField(vmStopOrchestrationService, "networkMgr", networkMgr); + ReflectionTestUtils.setField(vmStopOrchestrationService, "vmWorkJobQueueService", vmWorkJobQueueService); + ReflectionTestUtils.setField(vmStopOrchestrationService, "vmStopCommandService", vmStopCommandService); + ReflectionTestUtils.setField(vmStopOrchestrationService, "virtualMachineManager", virtualMachineManagerImpl); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmStopOrchestrationService", vmStopOrchestrationService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmMigrationCheckpointService", vmMigrationCheckpointService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmAllocationOrchestrationService", vmAllocationOrchestrationService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmMigrateAwayPlanningService", vmMigrateAwayPlanningService); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmNetworkAttachmentOrchestrationService", vmNetworkAttachmentOrchestrationService); } @After @@ -378,10 +491,32 @@ public void testaddHostIpToCertDetailsIfConfigAllowsWhenConfigFalse() { assertEquals(routerIp, ipAddresses.get(NetworkElementCommand.ROUTER_IP)); } - @Test(expected = CloudRuntimeException.class) - public void testScaleVM3() throws Exception { + @Test + public void findHostAndMigrateDelegatesToScaleReconfigurationService() throws Exception { DeploymentPlanner.ExcludeList excludeHostList = new DeploymentPlanner.ExcludeList(); virtualMachineManagerImpl.findHostAndMigrate(vmInstanceMock.getUuid(), 2l, null, excludeHostList); + + verify(vmScaleReconfigurationService).findHostAndMigrate(vmInstanceMock.getUuid(), 2L, null, excludeHostList); + } + + @Test + public void migrateForScaleDelegatesToScaleReconfigurationService() throws Exception { + DeployDestination dest = mock(DeployDestination.class); + + virtualMachineManagerImpl.migrateForScale(vmInstanceMock.getUuid(), hostMockId, dest, 2L); + + verify(vmScaleReconfigurationService).migrateForScale(vmInstanceMock.getUuid(), hostMockId, dest, 2L); + } + + @Test + public void reConfigureVmDelegatesToScaleReconfigurationService() throws Exception { + Map customParameters = new HashMap<>(); + when(vmScaleReconfigurationService.reConfigureVm(vmInstanceMock.getUuid(), serviceOfferingMock, serviceOfferingMock, customParameters, true)).thenReturn(vmInstanceMock); + + VMInstanceVO result = virtualMachineManagerImpl.reConfigureVm(vmInstanceMock.getUuid(), serviceOfferingMock, serviceOfferingMock, customParameters, true); + + assertSame(vmInstanceMock, result); + verify(vmScaleReconfigurationService).reConfigureVm(vmInstanceMock.getUuid(), serviceOfferingMock, serviceOfferingMock, customParameters, true); } @Test @@ -458,633 +593,345 @@ public void testExeceuteInSequenceVmware() throws IllegalAccessException, NoSuch } @Test - public void testCheckIfCanUpgrade() throws Exception { - when(vmInstanceMock.getState()).thenReturn(State.Stopped); - when(serviceOfferingMock.isDynamic()).thenReturn(true); - when(vmInstanceMock.getServiceOfferingId()).thenReturn(1l); - - ServiceOfferingVO mockCurrentServiceOffering = mock(ServiceOfferingVO.class); - DiskOfferingVO mockCurrentDiskOffering = mock(DiskOfferingVO.class); - - when(serviceOfferingDaoMock.findByIdIncludingRemoved(anyLong(), anyLong())).thenReturn(mockCurrentServiceOffering); - when(diskOfferingDaoMock.findByIdIncludingRemoved(anyLong())).thenReturn(mockCurrentDiskOffering); - when(diskOfferingDaoMock.findById(anyLong())).thenReturn(diskOfferingMock); - when(diskOfferingMock.isUseLocalStorage()).thenReturn(false); - when(mockCurrentServiceOffering.isSystemUse()).thenReturn(true); - when(serviceOfferingMock.isSystemUse()).thenReturn(true); - String[] oldDOStorageTags = {"x","y"}; - String[] newDOStorageTags = {"z","x","y"}; - when(mockCurrentDiskOffering.getTagsArray()).thenReturn(oldDOStorageTags); - when(diskOfferingMock.getTagsArray()).thenReturn(newDOStorageTags); + public void checkIfCanUpgradeDelegatesToServiceOfferingUpgradeManager() { + VmServiceOfferingUpgradeManager manager = mock(VmServiceOfferingUpgradeManager.class); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmServiceOfferingUpgradeManager", manager); virtualMachineManagerImpl.checkIfCanUpgrade(vmInstanceMock, serviceOfferingMock); - } - @Test(expected = InvalidParameterValueException.class) - public void testCheckIfCanUpgradeFail() { - when(serviceOfferingMock.getState()).thenReturn(ServiceOffering.State.Inactive); - - virtualMachineManagerImpl.checkIfCanUpgrade(vmInstanceMock, serviceOfferingMock); + verify(manager).checkIfCanUpgrade(vmInstanceMock, serviceOfferingMock); } @Test - public void isStorageCrossClusterMigrationTestStorageTypeEqualsCluster() { - Mockito.doReturn(2L).when(storagePoolVoMock).getClusterId(); - Mockito.doReturn(ScopeType.CLUSTER).when(storagePoolVoMock).getScope(); + public void createMappingVolumeAndStoragePoolDelegatesToPlanningService() { + Map userMap = new HashMap<>(); + Map expected = new HashMap<>(); + when(vmVolumeMigrationPlanningService.createMappingVolumeAndStoragePool(virtualMachineProfileMock, hostMock, userMap)).thenReturn(expected); - boolean returnedValue = virtualMachineManagerImpl.isStorageCrossClusterMigration(1L, storagePoolVoMock); + Map result = virtualMachineManagerImpl.createMappingVolumeAndStoragePool(virtualMachineProfileMock, hostMock, userMap); - Assert.assertTrue(returnedValue); + assertEquals(expected, result); + verify(vmVolumeMigrationPlanningService).createMappingVolumeAndStoragePool(virtualMachineProfileMock, hostMock, userMap); } @Test - public void isStorageCrossClusterMigrationTestStorageSameCluster() { - Mockito.doReturn(1L).when(storagePoolVoMock).getClusterId(); - Mockito.doReturn(ScopeType.CLUSTER).when(storagePoolVoMock).getScope(); + public void findVolumesThatWereNotMappedByTheUserDelegatesToPlanningServiceImpl() { + Map mapped = new HashMap<>(); + List expected = new ArrayList<>(); + when(vmVolumeMigrationPlanningServiceImpl.findVolumesThatWereNotMappedByTheUser(virtualMachineProfileMock, mapped)).thenReturn(expected); - boolean returnedValue = virtualMachineManagerImpl.isStorageCrossClusterMigration(1L, storagePoolVoMock); + List result = virtualMachineManagerImpl.findVolumesThatWereNotMappedByTheUser(virtualMachineProfileMock, mapped); - assertFalse(returnedValue); + assertEquals(expected, result); + verify(vmVolumeMigrationPlanningServiceImpl).findVolumesThatWereNotMappedByTheUser(virtualMachineProfileMock, mapped); } @Test - public void isStorageCrossClusterMigrationTestStorageTypeEqualsZone() { - Mockito.doReturn(ScopeType.ZONE).when(storagePoolVoMock).getScope(); + public void buildMapUsingUserInformationDelegatesToPlanningServiceImpl() { + Map userMap = new HashMap<>(); + Map expected = new HashMap<>(); + when(vmVolumeMigrationPlanningServiceImpl.buildMapUsingUserInformation(virtualMachineProfileMock, hostMock, userMap)).thenReturn(expected); - boolean returnedValue = virtualMachineManagerImpl.isStorageCrossClusterMigration(1L, storagePoolVoMock); + Map result = virtualMachineManagerImpl.buildMapUsingUserInformation(virtualMachineProfileMock, hostMock, userMap); - assertFalse(returnedValue); + assertEquals(expected, result); + verify(vmVolumeMigrationPlanningServiceImpl).buildMapUsingUserInformation(virtualMachineProfileMock, hostMock, userMap); } @Test - public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolNotManaged() { - Mockito.doReturn(false).when(storagePoolVoMock).isManaged(); + public void executeManagedStorageChecksWhenTargetStoragePoolProvidedDelegatesToPlanningServiceImpl() { + StoragePoolVO targetPool = Mockito.mock(StoragePoolVO.class); - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); + virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, targetPool); - verify(storagePoolVoMock).isManaged(); - verify(storagePoolVoMock, Mockito.times(0)).getId(); + verify(vmVolumeMigrationPlanningServiceImpl).executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, targetPool); } @Test - public void allowVolumeMigrationsForPowerFlexStorage() { - Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); - Mockito.doReturn(Storage.StoragePoolType.PowerFlex).when(storagePoolVoMock).getPoolType(); + public void createStoragePoolMappingsForVolumesDelegatesToPlanningServiceImpl() { + Map mapped = new HashMap<>(); + List unmapped = new ArrayList<>(); - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); + virtualMachineManagerImpl.createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, mapped, unmapped); - verify(storagePoolVoMock).isManaged(); - verify(storagePoolVoMock, Mockito.times(0)).getId(); + verify(vmVolumeMigrationPlanningServiceImpl).createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, mapped, unmapped); } @Test - public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolEqualsTargetPool() { - Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); - // return any storage type except powerflex/scaleio - List values = Arrays.asList(Storage.StoragePoolType.values()); - when(storagePoolVoMock.getPoolType()).thenAnswer((Answer) invocation -> { - List filteredValues = values.stream().filter(v -> v != Storage.StoragePoolType.PowerFlex).collect(Collectors.toList()); - int randomIndex = new Random().nextInt(filteredValues.size()); - return filteredValues.get(randomIndex); }); - - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, storagePoolVoMock); - - verify(storagePoolVoMock).isManaged(); - verify(storagePoolVoMock, Mockito.times(2)).getId(); - } + public void shouldMapVolumeDelegatesToPlanningServiceImpl() { + when(vmVolumeMigrationPlanningServiceImpl.shouldMapVolume(virtualMachineProfileMock, storagePoolVoMock)).thenReturn(true); - @Test(expected = CloudRuntimeException.class) - public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentStoragePoolNotEqualsTargetPool() { - Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); - // return any storage type except powerflex/scaleio - List values = Arrays.asList(Storage.StoragePoolType.values()); - when(storagePoolVoMock.getPoolType()).thenAnswer((Answer) invocation -> { - List filteredValues = values.stream().filter(v -> v != Storage.StoragePoolType.PowerFlex).collect(Collectors.toList()); - int randomIndex = new Random().nextInt(filteredValues.size()); - return filteredValues.get(randomIndex); }); + boolean result = virtualMachineManagerImpl.shouldMapVolume(virtualMachineProfileMock, storagePoolVoMock); - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); + assertTrue(result); + verify(vmVolumeMigrationPlanningServiceImpl).shouldMapVolume(virtualMachineProfileMock, storagePoolVoMock); } @Test - public void buildMapUsingUserInformationTestUserDefinedMigrationMapEmpty() { - HashMap userDefinedVolumeToStoragePoolMap = Mockito.spy(new HashMap<>()); - - Map volumeToPoolObjectMap = virtualMachineManagerImpl.buildMapUsingUserInformation(virtualMachineProfileMock, hostMock, userDefinedVolumeToStoragePoolMap); - - Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); + public void executeManagedStorageChecksWhenTargetStoragePoolNotProvidedDelegatesToPlanningServiceImpl() { + virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - verify(userDefinedVolumeToStoragePoolMap, times(0)).keySet(); + verify(vmVolumeMigrationPlanningServiceImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); } - @Test(expected = CloudRuntimeException.class) - public void buildMapUsingUserInformationTestTargetHostDoesNotHaveAccessToPool() { - HashMap userDefinedVolumeToStoragePoolMap = new HashMap<>(); - userDefinedVolumeToStoragePoolMap.put(volumeMockId, storagePoolVoMockId); - - Mockito.doNothing().when(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolProvided(any(StoragePoolVO.class), any(VolumeVO.class), any(StoragePoolVO.class)); - Mockito.doReturn(null).when(storagePoolHostDaoMock).findByPoolHost(storagePoolVoMockId, hostMockId); + @Test + public void isStorageCrossClusterMigrationDelegatesToPlanningServiceImpl() { + when(vmVolumeMigrationPlanningServiceImpl.isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock)).thenReturn(true); - virtualMachineManagerImpl.buildMapUsingUserInformation(virtualMachineProfileMock, hostMock, userDefinedVolumeToStoragePoolMap); + boolean result = virtualMachineManagerImpl.isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); + assertTrue(result); + verify(vmVolumeMigrationPlanningServiceImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); } @Test - public void buildMapUsingUserInformationTestTargetHostHasAccessToPool() { - HashMap userDefinedVolumeToStoragePoolMap = Mockito.spy(new HashMap<>()); - userDefinedVolumeToStoragePoolMap.put(volumeMockId, storagePoolVoMockId); - - Mockito.doNothing().when(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolProvided(any(StoragePoolVO.class), any(VolumeVO.class), - any(StoragePoolVO.class)); - Mockito.doReturn(Mockito.mock(StoragePoolHostVO.class)).when(storagePoolHostDaoMock).findByPoolHost(storagePoolVoMockId, hostMockId); + public void createVolumeToStoragePoolMappingIfPossibleDelegatesToPlanningServiceImpl() { + Map mapped = new HashMap<>(); - Map volumeToPoolObjectMap = virtualMachineManagerImpl.buildMapUsingUserInformation(virtualMachineProfileMock, hostMock, userDefinedVolumeToStoragePoolMap); + virtualMachineManagerImpl.createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, mapped, volumeVoMock, storagePoolVoMock); - assertFalse(volumeToPoolObjectMap.isEmpty()); - assertEquals(storagePoolVoMock, volumeToPoolObjectMap.get(volumeVoMock)); - - verify(userDefinedVolumeToStoragePoolMap, times(1)).keySet(); + verify(vmVolumeMigrationPlanningServiceImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, mapped, volumeVoMock, storagePoolVoMock); } @Test - public void findVolumesThatWereNotMappedByTheUserTest() { - Map volumeToStoragePoolObjectMap = Mockito.spy(new HashMap<>()); - volumeToStoragePoolObjectMap.put(volumeVoMock, storagePoolVoMock); - - Volume volumeVoMock2 = Mockito.mock(Volume.class); + public void getCandidateStoragePoolsToMigrateLocalVolumeDelegatesToPlanningServiceImpl() { + List expected = new ArrayList<>(); + when(vmVolumeMigrationPlanningServiceImpl.getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock)).thenReturn(expected); - List volumesOfVm = new ArrayList<>(); - volumesOfVm.add(volumeVoMock); - volumesOfVm.add(volumeVoMock2); + List result = virtualMachineManagerImpl.getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); - Mockito.doReturn(volumesOfVm).when(volumeDaoMock).findUsableVolumesForInstance(vmInstanceVoMockId); - List volumesNotMapped = virtualMachineManagerImpl.findVolumesThatWereNotMappedByTheUser(virtualMachineProfileMock, volumeToStoragePoolObjectMap); - - assertEquals(1, volumesNotMapped.size()); - assertEquals(volumeVoMock2, volumesNotMapped.get(0)); + assertEquals(expected, result); + verify(vmVolumeMigrationPlanningServiceImpl).getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); } @Test - public void executeManagedStorageChecksWhenTargetStoragePoolNotProvidedTestCurrentStoragePoolNotManaged() { - Mockito.doReturn(false).when(storagePoolVoMock).isManaged(); + public void checkIfNewOfferingStorageScopeMatchesStoragePoolDelegatesToServiceOfferingUpgradeManager() { + VmServiceOfferingUpgradeManager manager = mock(VmServiceOfferingUpgradeManager.class); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmServiceOfferingUpgradeManager", manager); - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); + virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, diskOfferingMock); - verify(storagePoolVoMock).isManaged(); - verify(storagePoolHostDaoMock, Mockito.times(0)).findByPoolHost(anyLong(), anyLong()); + verify(manager).checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, diskOfferingMock); } @Test - public void executeManagedStorageChecksWhenTargetStoragePoolNotProvidedTestCurrentStoragePoolManagedIsConnectedToHost() { - Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); - Mockito.doReturn(Mockito.mock(StoragePoolHostVO.class)).when(storagePoolHostDaoMock).findByPoolHost(storagePoolVoMockId, hostMockId); + public void isRootVolumeOnLocalStorageDelegatesToServiceOfferingUpgradeManager() { + VmServiceOfferingUpgradeManager manager = mock(VmServiceOfferingUpgradeManager.class); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "vmServiceOfferingUpgradeManager", manager); + when(manager.isRootVolumeOnLocalStorage(vmInstanceVoMockId)).thenReturn(true); - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - - verify(storagePoolVoMock).isManaged(); - verify(storagePoolHostDaoMock, Mockito.times(1)).findByPoolHost(storagePoolVoMockId, hostMockId); - } - - @Test(expected = CloudRuntimeException.class) - public void executeManagedStorageChecksWhenTargetStoragePoolNotProvidedTestCurrentStoragePoolManagedIsNotConnectedToHost() { - Mockito.doReturn(true).when(storagePoolVoMock).isManaged(); - Mockito.doReturn(null).when(storagePoolHostDaoMock).findByPoolHost(storagePoolVoMockId, hostMockId); + boolean result = virtualMachineManagerImpl.isRootVolumeOnLocalStorage(vmInstanceVoMockId); - virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); + assertTrue(result); + verify(manager).isRootVolumeOnLocalStorage(vmInstanceVoMockId); } @Test - public void getCandidateStoragePoolsToMigrateLocalVolumeTestLocalVolume() { - Mockito.doReturn(Mockito.mock(DiskOfferingVO.class)).when(diskOfferingDaoMock).findById(anyLong()); - - Mockito.doReturn(true).when(storagePoolVoMock).isLocal(); - - List poolListMock = new ArrayList<>(); - poolListMock.add(storagePoolVoMock); - - Mockito.doReturn(poolListMock).when(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); + public void checkIfTemplateNeededForCreatingVmVolumesDelegatesToAllocationService() { + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - List poolList = virtualMachineManagerImpl.getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); + virtualMachineManagerImpl.checkIfTemplateNeededForCreatingVmVolumes(vm); - assertEquals(1, poolList.size()); - assertEquals(storagePoolVoMock, poolList.get(0)); + verify(vmAllocationOrchestrationService).checkIfTemplateNeededForCreatingVmVolumes(vm); } @Test - public void getCandidateStoragePoolsToMigrateLocalVolumeTestCrossClusterMigration() { - Mockito.doReturn(Mockito.mock(DiskOfferingVO.class)).when(diskOfferingDaoMock).findById(anyLong()); - - Mockito.doReturn(false).when(storagePoolVoMock).isLocal(); - - List poolListMock = new ArrayList<>(); - poolListMock.add(storagePoolVoMock); - - Mockito.doReturn(poolListMock).when(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); + public void allocateDelegatesToAllocationService() throws Exception { + String vmInstanceName = "i-2-3-VM"; + VirtualMachineTemplate template = mock(VirtualMachineTemplate.class); + ServiceOffering serviceOffering = mock(ServiceOffering.class); + DiskOfferingInfo rootDiskOfferingInfo = mock(DiskOfferingInfo.class); + List dataDiskOfferings = new ArrayList<>(); + List dataDiskDeviceIds = new ArrayList<>(); + LinkedHashMap> networks = new LinkedHashMap<>(); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + Volume volume = mock(Volume.class); + Snapshot snapshot = mock(Snapshot.class); - Mockito.doReturn(true).when(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); - List poolList = virtualMachineManagerImpl.getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); + virtualMachineManagerImpl.allocate(vmInstanceName, template, serviceOffering, rootDiskOfferingInfo, dataDiskOfferings, + dataDiskDeviceIds, networks, plan, HypervisorType.KVM, null, null, volume, snapshot); - assertEquals(1, poolList.size()); - assertEquals(storagePoolVoMock, poolList.get(0)); + verify(vmAllocationOrchestrationService).allocate(vmInstanceName, template, serviceOffering, rootDiskOfferingInfo, + dataDiskOfferings, dataDiskDeviceIds, networks, plan, HypervisorType.KVM, null, null, volume, snapshot); } @Test - public void getCandidateStoragePoolsToMigrateLocalVolumeTestWithinClusterMigration() { - Mockito.doReturn(Mockito.mock(DiskOfferingVO.class)).when(diskOfferingDaoMock).findById(anyLong()); - - Mockito.doReturn(false).when(storagePoolVoMock).isLocal(); - - List poolListMock = new ArrayList<>(); - poolListMock.add(storagePoolVoMock); - - Mockito.doReturn(poolListMock).when(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); + public void migrateAwayDelegatesToMigrateAwayPlanningService() throws Exception { + virtualMachineManagerImpl.migrateAway(vmMockUuid, hostMockId); - Mockito.doReturn(false).when(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); - List poolList = virtualMachineManagerImpl.getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); - - Assert.assertTrue(poolList.isEmpty()); + verify(vmMigrateAwayPlanningService).migrateAway(vmMockUuid, hostMockId); } @Test - public void getCandidateStoragePoolsToMigrateLocalVolumeTestMoreThanOneAllocator() { - StoragePoolAllocator storagePoolAllocatorMock2 = Mockito.mock(StoragePoolAllocator.class); - StoragePoolAllocator storagePoolAllocatorMock3 = Mockito.mock(StoragePoolAllocator.class); - - List storagePoolAllocatorsMock = new ArrayList<>(); - storagePoolAllocatorsMock.add(storagePoolAllocatorMock); - storagePoolAllocatorsMock.add(storagePoolAllocatorMock2); - storagePoolAllocatorsMock.add(storagePoolAllocatorMock3); - - virtualMachineManagerImpl.setStoragePoolAllocators(storagePoolAllocatorsMock); - - Mockito.doReturn(Mockito.mock(DiskOfferingVO.class)).when(diskOfferingDaoMock).findById(anyLong()); + public void checkIfVmHasClusterWideVolumesDelegatesToMigrateAwayPlanningService() { + when(vmMigrateAwayPlanningService.checkIfVmHasClusterWideVolumes(vmInstanceVoMockId)).thenReturn(true); - Mockito.doReturn(false).when(storagePoolVoMock).isLocal(); + boolean result = virtualMachineManagerImpl.checkIfVmHasClusterWideVolumes(vmInstanceVoMockId); - List poolListMock = new ArrayList<>(); - poolListMock.add(storagePoolVoMock); - - Mockito.doReturn(poolListMock).when(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - - Mockito.doReturn(null).when(storagePoolAllocatorMock2).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - - Mockito.doReturn(new ArrayList<>()).when(storagePoolAllocatorMock3).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - - Mockito.doReturn(false).when(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); - List poolList = virtualMachineManagerImpl.getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); - - Assert.assertTrue(poolList.isEmpty()); - - verify(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - verify(storagePoolAllocatorMock2).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - verify(storagePoolAllocatorMock3).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - } - - @Test(expected = CloudRuntimeException.class) - public void createVolumeToStoragePoolMappingIfPossibleTestNotStoragePoolsAvailable() { - Mockito.doReturn(null).when(virtualMachineManagerImpl).getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); - - virtualMachineManagerImpl.createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, new HashMap<>(), volumeVoMock, storagePoolVoMock); + assertTrue(result); + verify(vmMigrateAwayPlanningService).checkIfVmHasClusterWideVolumes(vmInstanceVoMockId); } @Test - public void createVolumeToStoragePoolMappingIfPossibleTestTargetHostAccessCurrentStoragePool() { - List storagePoolList = new ArrayList<>(); - storagePoolList.add(storagePoolVoMock); - - Mockito.doReturn(storagePoolList).when(virtualMachineManagerImpl).getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); + public void getMigrationDeploymentDelegatesToMigrateAwayPlanningService() { + ExcludeList excludes = new ExcludeList(); + when(vmMigrateAwayPlanningService.getMigrationDeployment(vmInstanceMock, hostMock, storagePoolVoMockId, excludes)) + .thenReturn(dataCenterDeploymentMock); - HashMap volumeToPoolObjectMap = new HashMap<>(); - virtualMachineManagerImpl.createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); + DataCenterDeployment result = virtualMachineManagerImpl.getMigrationDeployment(vmInstanceMock, hostMock, storagePoolVoMockId, excludes); - Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); + assertSame(dataCenterDeploymentMock, result); + verify(vmMigrateAwayPlanningService).getMigrationDeployment(vmInstanceMock, hostMock, storagePoolVoMockId, excludes); } @Test - public void createVolumeToStoragePoolMappingIfPossibleTestTargetHostDoesNotAccessCurrentStoragePool() { - StoragePoolVO storagePoolMockOther = Mockito.mock(StoragePoolVO.class); - String storagePoolMockOtherUuid = "storagePoolMockOtherUuid"; - Mockito.doReturn(storagePoolMockOtherUuid).when(storagePoolMockOther).getUuid(); - Mockito.doReturn(storagePoolMockOther).when(storagePoolDaoMock).findByUuid(storagePoolMockOtherUuid); + public void checkAndAttemptMigrateVmAcrossClusterNonValid() { + // Below scenarios shouldn't result in VM migration - List storagePoolList = new ArrayList<>(); - storagePoolList.add(storagePoolMockOther); + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, 1L, new HashMap<>()); - Mockito.doReturn(storagePoolList).when(virtualMachineManagerImpl).getCandidateStoragePoolsToMigrateLocalVolume(virtualMachineProfileMock, dataCenterDeploymentMock, volumeVoMock); + Mockito.when(vm.getHypervisorType()).thenReturn(HypervisorType.VMware); + Mockito.when(vm.getLastHostId()).thenReturn(null); + virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, 1L, new HashMap<>()); - HashMap volumeToPoolObjectMap = new HashMap<>(); - virtualMachineManagerImpl.createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); + Long destinationClusterId = 10L; + Mockito.when(vm.getLastHostId()).thenReturn(1L); + HostVO hostVO = Mockito.mock(HostVO.class); + Mockito.when(hostVO.getClusterId()).thenReturn(destinationClusterId); + Mockito.when(hostDaoMock.findById(1L)).thenReturn(hostVO); + virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, destinationClusterId, new HashMap<>()); - assertFalse(volumeToPoolObjectMap.isEmpty()); - assertEquals(storagePoolMockOther, volumeToPoolObjectMap.get(volumeVoMock)); + destinationClusterId = 20L; + Map map = new HashMap<>(); + StoragePool pool1 = Mockito.mock(StoragePool.class); + Mockito.when(pool1.getClusterId()).thenReturn(10L); + map.put(Mockito.mock(Volume.class), pool1); + StoragePool pool2 = Mockito.mock(StoragePool.class); + Mockito.when(pool2.getClusterId()).thenReturn(null); + map.put(Mockito.mock(Volume.class), pool2); + virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, destinationClusterId, map); } @Test - public void createStoragePoolMappingsForVolumesTestLocalStoragevolume() { - ArrayList allVolumes = new ArrayList<>(); - allVolumes.add(volumeVoMock); - - HashMap volumeToPoolObjectMap = new HashMap<>(); - - Mockito.doReturn(ScopeType.HOST).when(storagePoolVoMock).getScope(); - Mockito.doNothing().when(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.doNothing().when(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, - storagePoolVoMock); + public void setVmNetworkDetailsDelegatesToNetworkNameMappingService() { + VMInstanceVO vm = mock(VMInstanceVO.class); + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); - virtualMachineManagerImpl.createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, allVolumes); + virtualMachineManagerImpl.setVmNetworkDetails(vm, vmTO); - Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); - verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - verify(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); + verify(vmNetworkNameMappingService).setVmNetworkDetails(vm, vmTO); } @Test - public void createStoragePoolMappingsForVolumesTestCrossCluterMigration() { - ArrayList allVolumes = new ArrayList<>(); - allVolumes.add(volumeVoMock); + public void updateOverCommitRatioForVmProfileDelegatesToStartProfilePreparationService() { + virtualMachineManagerImpl.updateOverCommitRatioForVmProfile(virtualMachineProfileMock, clusterMockId); - HashMap volumeToPoolObjectMap = new HashMap<>(); - - Mockito.doReturn(ScopeType.CLUSTER).when(storagePoolVoMock).getScope(); - Mockito.doNothing().when(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.doNothing().when(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); - Mockito.doReturn(true).when(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); - - virtualMachineManagerImpl.createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, allVolumes); - - Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); - verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - verify(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); - verify(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); + verify(vmStartProfilePreparationService).updateOverCommitRatioForVmProfile(virtualMachineProfileMock, clusterMockId); } @Test - public void createStoragePoolMappingsForVolumesTestNotCrossCluterMigrationWithClusterStorage() { - ArrayList allVolumes = new ArrayList<>(); - allVolumes.add(volumeVoMock); - - HashMap volumeToPoolObjectMap = new HashMap<>(); + public void conditionallySetPodToDeployInDelegatesToStartProfilePreparationService() { + virtualMachineManagerImpl.conditionallySetPodToDeployIn(vmInstanceMock); - Mockito.doReturn(ScopeType.CLUSTER).when(storagePoolVoMock).getScope(); - Mockito.doNothing().when(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(any(), any(), any()); - Mockito.doReturn(false).when(virtualMachineManagerImpl).isStorageCrossClusterMigration(anyLong(), any()); - - virtualMachineManagerImpl.createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, allVolumes); - - assertFalse(volumeToPoolObjectMap.isEmpty()); - assertEquals(storagePoolVoMock, volumeToPoolObjectMap.get(volumeVoMock)); - - verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - verify(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); - verify(virtualMachineManagerImpl, Mockito.times(0)).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, - storagePoolVoMock); + verify(vmStartProfilePreparationService).conditionallySetPodToDeployIn(vmInstanceMock); } @Test - public void createMappingVolumeAndStoragePoolTest() { - Map volumeToPoolObjectMap = new HashMap<>(); - List volumesNotMapped = new ArrayList<>(); - - Mockito.doReturn(volumeToPoolObjectMap).when(virtualMachineManagerImpl).buildMapUsingUserInformation(Mockito.eq(virtualMachineProfileMock), Mockito.eq(hostMock), - Mockito.anyMap()); + public void areAllVolumesAllocatedDelegatesToStartProfilePreparationService() { + when(vmStartProfilePreparationService.areAllVolumesAllocated(vmInstanceVoMockId)).thenReturn(true); - Mockito.doReturn(volumesNotMapped).when(virtualMachineManagerImpl).findVolumesThatWereNotMappedByTheUser(virtualMachineProfileMock, volumeToPoolObjectMap); - Mockito.doNothing().when(virtualMachineManagerImpl).createStoragePoolMappingsForVolumes(Mockito.eq(virtualMachineProfileMock), - any(DataCenterDeployment.class), Mockito.eq(volumeToPoolObjectMap), Mockito.eq(volumesNotMapped)); + boolean result = virtualMachineManagerImpl.areAllVolumesAllocated(vmInstanceVoMockId); - Map mappingVolumeAndStoragePool = virtualMachineManagerImpl.createMappingVolumeAndStoragePool(virtualMachineProfileMock, hostMock, new HashMap<>()); - - assertEquals(mappingVolumeAndStoragePool, volumeToPoolObjectMap); - - InOrder inOrder = Mockito.inOrder(virtualMachineManagerImpl); - inOrder.verify(virtualMachineManagerImpl).buildMapUsingUserInformation(Mockito.eq(virtualMachineProfileMock), Mockito.eq(hostMock), Mockito.anyMap()); - inOrder.verify(virtualMachineManagerImpl).findVolumesThatWereNotMappedByTheUser(virtualMachineProfileMock, volumeToPoolObjectMap); - inOrder.verify(virtualMachineManagerImpl).createStoragePoolMappingsForVolumes(Mockito.eq(virtualMachineProfileMock), - any(DataCenterDeployment.class), Mockito.eq(volumeToPoolObjectMap), Mockito.eq(volumesNotMapped)); + assertTrue(result); + verify(vmStartProfilePreparationService).areAllVolumesAllocated(vmInstanceVoMockId); } @Test - public void matchesOfSorts() { - List nothing = null; - List empty = new ArrayList<>(); - List tag = Arrays.asList("bla"); - List tags = Arrays.asList("bla", "blob"); - List others = Arrays.asList("bla", "blieb"); - List three = Arrays.asList("bla", "blob", "blieb"); - - // single match - assertTrue(VirtualMachineManagerImpl.matches(tag,tags)); - assertTrue(VirtualMachineManagerImpl.matches(tag,others)); - - // no requirements - assertTrue(VirtualMachineManagerImpl.matches(nothing,tags)); - assertTrue(VirtualMachineManagerImpl.matches(empty,tag)); - - // mis(sing)match - assertFalse(VirtualMachineManagerImpl.matches(tags,tag)); - assertFalse(VirtualMachineManagerImpl.matches(tag,nothing)); - assertFalse(VirtualMachineManagerImpl.matches(tag,empty)); + public void logBootModeParametersDelegatesToStartProfilePreparationService() { + Map params = new HashMap<>(); - // disjunct sets - assertFalse(VirtualMachineManagerImpl.matches(tags,others)); - assertFalse(VirtualMachineManagerImpl.matches(others,tags)); + virtualMachineManagerImpl.logBootModeParameters(params); - // everything matches the larger set - assertTrue(VirtualMachineManagerImpl.matches(nothing,three)); - assertTrue(VirtualMachineManagerImpl.matches(empty,three)); - assertTrue(VirtualMachineManagerImpl.matches(tag,three)); - assertTrue(VirtualMachineManagerImpl.matches(tags,three)); - assertTrue(VirtualMachineManagerImpl.matches(others,three)); + verify(vmStartProfilePreparationService).logBootModeParameters(params); } @Test - public void isRootVolumeOnLocalStorageTestOnLocal() { - prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.HOST, true); - } + public void resetVmNicsDeviceIdDelegatesToStartProfilePreparationService() { + virtualMachineManagerImpl.resetVmNicsDeviceId(vmInstanceVoMockId); - @Test - public void isRootVolumeOnLocalStorageTestCluster() { - prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.CLUSTER, false); + verify(vmStartProfilePreparationService).resetVmNicsDeviceId(vmInstanceVoMockId); } @Test - public void isRootVolumeOnLocalStorageTestZone() { - prepareAndTestIsRootVolumeOnLocalStorage(ScopeType.ZONE, false); - } - - private void prepareAndTestIsRootVolumeOnLocalStorage(ScopeType scope, boolean expected) { - StoragePoolVO storagePoolVoMock = Mockito.mock(StoragePoolVO.class); - Mockito.doReturn(storagePoolVoMock).when(storagePoolDaoMock).findById(anyLong()); - Mockito.doReturn(scope).when(storagePoolVoMock).getScope(); - List mockedVolumes = new ArrayList<>(); - mockedVolumes.add(volumeVoMock); - Mockito.doReturn(mockedVolumes).when(volumeDaoMock).findByInstanceAndType(anyLong(), any()); - - boolean result = virtualMachineManagerImpl.isRootVolumeOnLocalStorage(0l); + public void replugNicDelegatesToNicBackendCommandService() throws Exception { + Network network = mock(Network.class); + NicTO nic = mock(NicTO.class); + VirtualMachineTO vm = mock(VirtualMachineTO.class); + Host host = mock(Host.class); + when(vmNicBackendCommandService.replugNic(network, nic, vm, host)).thenReturn(true); - assertEquals(expected, result); - } + boolean result = virtualMachineManagerImpl.replugNic(network, nic, vm, host); - @Test - public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalLocal() { - prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, true); + assertTrue(result); + verify(vmNicBackendCommandService).replugNic(network, nic, vm, host); } @Test - public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedShared() { - prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, false); - } - - @Test (expected = InvalidParameterValueException.class) - public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestLocalShared() { - prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(true, false); - } + public void plugNicDelegatesToNicBackendCommandService() throws Exception { + Network network = mock(Network.class); + NicTO nic = mock(NicTO.class); + VirtualMachineTO vm = mock(VirtualMachineTO.class); + ReservationContext context = mock(ReservationContext.class); + DeployDestination dest = mock(DeployDestination.class); + when(vmNicBackendCommandService.plugNic(network, nic, vm, context, dest)).thenReturn(true); - @Test (expected = InvalidParameterValueException.class) - public void checkIfNewOfferingStorageScopeMatchesStoragePoolTestSharedLocal() { - prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(false, true); - } + boolean result = virtualMachineManagerImpl.plugNic(network, nic, vm, context, dest); - private void prepareAndRunCheckIfNewOfferingStorageScopeMatchesStoragePool(boolean isRootOnLocal, boolean isOfferingUsingLocal) { - Mockito.doReturn(isRootOnLocal).when(virtualMachineManagerImpl).isRootVolumeOnLocalStorage(anyLong()); - Mockito.doReturn("vmInstanceMockedToString").when(vmInstanceMock).toString(); - Mockito.doReturn(isOfferingUsingLocal).when(diskOfferingMock).isUseLocalStorage(); - virtualMachineManagerImpl.checkIfNewOfferingStorageScopeMatchesStoragePool(vmInstanceMock, diskOfferingMock); + assertTrue(result); + verify(vmNicBackendCommandService).plugNic(network, nic, vm, context, dest); } @Test - public void checkIfTemplateNeededForCreatingVmVolumesExistingRootVolumes() { - long vmId = 1L; - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vm.getId()).thenReturn(vmId); - Mockito.when(volumeDaoMock.findReadyRootVolumesByInstance(vmId)).thenReturn(List.of(Mockito.mock(VolumeVO.class))); - virtualMachineManagerImpl.checkIfTemplateNeededForCreatingVmVolumes(vm); - } + public void unplugNicDelegatesToNicBackendCommandService() throws Exception { + Network network = mock(Network.class); + NicTO nic = mock(NicTO.class); + VirtualMachineTO vm = mock(VirtualMachineTO.class); + ReservationContext context = mock(ReservationContext.class); + DeployDestination dest = mock(DeployDestination.class); + when(vmNicBackendCommandService.unplugNic(network, nic, vm, context, dest)).thenReturn(true); - @Test(expected = CloudRuntimeException.class) - public void checkIfTemplateNeededForCreatingVmVolumesMissingTemplate() { - long vmId = 1L; - long templateId = 1L; - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vm.getId()).thenReturn(vmId); - Mockito.when(vm.getTemplateId()).thenReturn(templateId); - Mockito.when(volumeDaoMock.findReadyRootVolumesByInstance(vmId)).thenReturn(null); - Mockito.when(templateDao.findById(templateId)).thenReturn(null); - virtualMachineManagerImpl.checkIfTemplateNeededForCreatingVmVolumes(vm); - } + boolean result = virtualMachineManagerImpl.unplugNic(network, nic, vm, context, dest); - @Test(expected = CloudRuntimeException.class) - public void checkIfTemplateNeededForCreatingVmVolumesMissingZoneTemplate() { - long vmId = 1L; - long templateId = 1L; - long dcId = 1L; - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vm.getId()).thenReturn(vmId); - Mockito.when(vm.getTemplateId()).thenReturn(templateId); - Mockito.when(vm.getDataCenterId()).thenReturn(dcId); - Mockito.when(volumeDaoMock.findReadyRootVolumesByInstance(vmId)).thenReturn(null); - VMTemplateVO template = Mockito.mock(VMTemplateVO.class); - Mockito.when(vm.getId()).thenReturn(templateId); - Mockito.when(templateDao.findById(templateId)).thenReturn(template); - virtualMachineManagerImpl.checkIfTemplateNeededForCreatingVmVolumes(vm); + assertTrue(result); + verify(vmNicBackendCommandService).unplugNic(network, nic, vm, context, dest); } @Test - public void checkIfTemplateNeededForCreatingVmVolumesTemplateAvailable() { - long vmId = 1L; - long templateId = 1L; - long dcId = 1L; - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vm.getId()).thenReturn(vmId); - Mockito.when(vm.getTemplateId()).thenReturn(templateId); - Mockito.when(vm.getDataCenterId()).thenReturn(dcId); - Mockito.when(volumeDaoMock.findReadyRootVolumesByInstance(vmId)).thenReturn(new ArrayList<>()); - VMTemplateVO template = Mockito.mock(VMTemplateVO.class); - Mockito.when(template.getId()).thenReturn(templateId); - Mockito.when(templateDao.findById(templateId)).thenReturn(template); - Mockito.when(templateZoneDao.findByZoneTemplate(dcId, templateId)).thenReturn(Mockito.mock(VMTemplateZoneVO.class)); - virtualMachineManagerImpl.checkIfTemplateNeededForCreatingVmVolumes(vm); + public void expungeDelegatesToExpungeOrchestrationService() throws Exception { + virtualMachineManagerImpl.expunge(vmMockUuid); + + verify(vmExpungeOrchestrationService).expunge(vmMockUuid); } @Test - public void checkAndAttemptMigrateVmAcrossClusterNonValid() { - // Below scenarios shouldn't result in VM migration + public void advanceExpungeDelegatesToExpungeOrchestrationService() throws Exception { + virtualMachineManagerImpl.advanceExpunge(vmMockUuid); - VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); - Mockito.when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); - virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, 1L, new HashMap<>()); - - Mockito.when(vm.getHypervisorType()).thenReturn(HypervisorType.VMware); - Mockito.when(vm.getLastHostId()).thenReturn(null); - virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, 1L, new HashMap<>()); - - Long destinationClusterId = 10L; - Mockito.when(vm.getLastHostId()).thenReturn(1L); - HostVO hostVO = Mockito.mock(HostVO.class); - Mockito.when(hostVO.getClusterId()).thenReturn(destinationClusterId); - Mockito.when(hostDaoMock.findById(1L)).thenReturn(hostVO); - virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, destinationClusterId, new HashMap<>()); - - destinationClusterId = 20L; - Map map = new HashMap<>(); - StoragePool pool1 = Mockito.mock(StoragePool.class); - Mockito.when(pool1.getClusterId()).thenReturn(10L); - map.put(Mockito.mock(Volume.class), pool1); - StoragePool pool2 = Mockito.mock(StoragePool.class); - Mockito.when(pool2.getClusterId()).thenReturn(null); - map.put(Mockito.mock(Volume.class), pool2); - virtualMachineManagerImpl.checkAndAttemptMigrateVmAcrossCluster(vm, destinationClusterId, map); + verify(vmExpungeOrchestrationService).advanceExpunge(vmMockUuid); } @Test - public void checkIfVmNetworkDetailsReturnedIsCorrect() { - VMInstanceVO vm = new VMInstanceVO(1L, 1L, "VM1", "i-2-2-VM", - VirtualMachine.Type.User, 1L, HypervisorType.KVM, 1L, 1L, 1L, - 1L, false, false); - - VirtualMachineTO vmTO = new VirtualMachineTO() { - }; - UserVmJoinVO userVm = new UserVmJoinVO(); - NetworkVO networkVO = mock(NetworkVO.class); - AccountVO accountVO = mock(AccountVO.class); - DomainVO domainVO = mock(DomainVO.class); - domainVO.setName("testDomain"); - DataCenterVO dataCenterVO = mock(DataCenterVO.class); - VpcVO vpcVO = mock(VpcVO.class); - - networkVO.setAccountId(1L); - networkVO.setName("testNet"); - networkVO.setVpcId(1L); - - accountVO.setAccountName("testAcc"); - - vpcVO.setName("VPC1"); + public void toNicTODelegatesToNetworkAttachmentOrchestrationService() { + NicProfile nic = mock(NicProfile.class); + NicTO nicTO = mock(NicTO.class); + when(vmNetworkAttachmentOrchestrationService.toNicTO(nic, HypervisorType.KVM)).thenReturn(nicTO); + NicTO result = virtualMachineManagerImpl.toNicTO(nic, HypervisorType.KVM); - List userVms = List.of(userVm); - Mockito.when(userVmJoinDaoMock.searchByIds(anyLong())).thenReturn(userVms); - Mockito.when(networkDao.findById(anyLong())).thenReturn(networkVO); - Mockito.when(accountDao.findById(anyLong())).thenReturn(accountVO); - Mockito.when(domainDao.findById(anyLong())).thenReturn(domainVO); - Mockito.when(dcDao.findById(anyLong())).thenReturn(dataCenterVO); - Mockito.when(vpcDao.findById(anyLong())).thenReturn(vpcVO); - Mockito.when(dataCenterVO.getId()).thenReturn(1L); - when(accountVO.getId()).thenReturn(2L); - Mockito.when(domainVO.getId()).thenReturn(3L); - Mockito.when(vpcVO.getId()).thenReturn(4L); - Mockito.when(networkVO.getId()).thenReturn(5L); - virtualMachineManagerImpl.setVmNetworkDetails(vm, vmTO); - assertEquals(1, vmTO.getNetworkIdToNetworkNameMap().size()); - assertEquals("D3-A2-Z1-V4-S5", vmTO.getNetworkIdToNetworkNameMap().get(5L)); + assertEquals(nicTO, result); + verify(vmNetworkAttachmentOrchestrationService).toNicTO(nic, HypervisorType.KVM); } @Test @@ -1157,15 +1004,7 @@ public void testOrchestrateStartNonNullPodId() throws Exception { Cluster cluster = mock(Cluster.class); when(dest.getCluster()).thenReturn(cluster); - ClusterDetailsVO cluster_detail_cpu = mock(ClusterDetailsVO.class); - ClusterDetailsVO cluster_detail_ram = mock(ClusterDetailsVO.class); when(cluster.getId()).thenReturn(1L); - when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_cpu); - when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_ram); - when(vmInstanceDetailsDao.findDetail(anyLong(), Mockito.anyString())).thenReturn(null); - when(cluster_detail_cpu.getValue()).thenReturn("1.0"); - when(cluster_detail_ram.getValue()).thenReturn("1.0"); - doReturn(false).when(virtualMachineManagerImpl).areAllVolumesAllocated(Mockito.anyLong()); CallContext callContext = mock(CallContext.class); when(callContext.getCallingAccount()).thenReturn(account); @@ -1253,15 +1092,12 @@ public void testOrchestrateStartNullPodId() throws Exception { Cluster cluster = mock(Cluster.class); when(dest.getCluster()).thenReturn(cluster); - ClusterDetailsVO cluster_detail_cpu = mock(ClusterDetailsVO.class); - ClusterDetailsVO cluster_detail_ram = mock(ClusterDetailsVO.class); when(cluster.getId()).thenReturn(1L); - when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_cpu); - when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_ram); - when(vmInstanceDetailsDao.findDetail(anyLong(), Mockito.anyString())).thenReturn(null); - when(cluster_detail_cpu.getValue()).thenReturn("1.0"); - when(cluster_detail_ram.getValue()).thenReturn("1.0"); - doReturn(true).when(virtualMachineManagerImpl).areAllVolumesAllocated(Mockito.anyLong()); + Mockito.doAnswer(invocation -> { + VMInstanceVO vm = invocation.getArgument(0); + vm.setPodIdToDeployIn(null); + return null; + }).when(vmStartProfilePreparationService).conditionallySetPodToDeployIn(vmInstance); CallContext callContext = mock(CallContext.class); when(callContext.getCallingAccount()).thenReturn(account); @@ -1280,46 +1116,47 @@ public void testOrchestrateStartNullPodId() throws Exception { } @Test - public void testIsDiskOfferingSuitableForVmSuccess() { - Mockito.doReturn(Mockito.mock(DiskOfferingVO.class)).when(diskOfferingDaoMock).findById(anyLong()); - List poolListMock = new ArrayList<>(); - poolListMock.add(storagePoolVoMock); - Mockito.doReturn(poolListMock).when(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(1)); - boolean result = virtualMachineManagerImpl.isDiskOfferingSuitableForVm(vmInstanceMock, virtualMachineProfileMock, 1L, 1L, 1L, 1L); - assertTrue(result); + public void testFindClusterAndHostIdForVmDelegatesToDiskOfferingSuitabilityService() { + Pair expected = new Pair<>(clusterMockId, hostMockId); + when(vmDiskOfferingSuitabilityService.findClusterAndHostIdForVm(vmInstanceMock, true)).thenReturn(expected); + + Pair result = virtualMachineManagerImpl.findClusterAndHostIdForVm(vmInstanceMock, true); + + assertEquals(expected, result); + verify(vmDiskOfferingSuitabilityService).findClusterAndHostIdForVm(vmInstanceMock, true); } @Test - public void testIsDiskOfferingSuitableForVmNegative() { - Mockito.doReturn(Mockito.mock(DiskOfferingVO.class)).when(diskOfferingDaoMock).findById(anyLong()); - Mockito.doReturn(new ArrayList<>()).when(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), - any(ExcludeList.class), Mockito.eq(1)); - boolean result = virtualMachineManagerImpl.isDiskOfferingSuitableForVm(vmInstanceMock, virtualMachineProfileMock, 1L, 1L, 1L, 1L); - assertFalse(result); + public void testFindClusterAndHostIdForVmByIdDelegatesToDiskOfferingSuitabilityService() { + Pair expected = new Pair<>(clusterMockId, hostMockId); + when(vmDiskOfferingSuitabilityService.findClusterAndHostIdForVm(vmInstanceVoMockId)).thenReturn(expected); + + Pair result = virtualMachineManagerImpl.findClusterAndHostIdForVm(vmInstanceVoMockId); + + assertEquals(expected, result); + verify(vmDiskOfferingSuitabilityService).findClusterAndHostIdForVm(vmInstanceVoMockId); + } + + @Test + public void testIsDiskOfferingSuitableForVmDelegatesToDiskOfferingSuitabilityService() { + when(vmDiskOfferingSuitabilityService.isDiskOfferingSuitableForVm(vmInstanceMock, virtualMachineProfileMock, 1L, 2L, 3L, 4L)).thenReturn(true); + + boolean result = virtualMachineManagerImpl.isDiskOfferingSuitableForVm(vmInstanceMock, virtualMachineProfileMock, 1L, 2L, 3L, 4L); + + assertTrue(result); + verify(vmDiskOfferingSuitabilityService).isDiskOfferingSuitableForVm(vmInstanceMock, virtualMachineProfileMock, 1L, 2L, 3L, 4L); } @Test - public void testGetDiskOfferingSuitabilityForVm() { - Mockito.doReturn(vmInstanceMock).when(vmInstanceDaoMock).findById(1L); - Mockito.when(vmInstanceMock.getHostId()).thenReturn(1L); - Mockito.doReturn(hostMock).when(hostDaoMock).findById(1L); - Mockito.when(hostMock.getClusterId()).thenReturn(1L); - ClusterVO cluster = Mockito.mock(ClusterVO.class); - Mockito.when(cluster.getPodId()).thenReturn(1L); - Mockito.doReturn(cluster).when(clusterDao).findById(1L); + public void testGetDiskOfferingSuitabilityForVmDelegatesToDiskOfferingSuitabilityService() { List diskOfferingIds = List.of(1L, 2L); - Mockito.doReturn(false).when(virtualMachineManagerImpl) - .isDiskOfferingSuitableForVm(eq(vmInstanceMock), any(VirtualMachineProfile.class), - eq(1L), eq(1L), eq(1L), eq(1L)); - Mockito.doReturn(true).when(virtualMachineManagerImpl) - .isDiskOfferingSuitableForVm(eq(vmInstanceMock), any(VirtualMachineProfile.class), - eq(1L), eq(1L), eq(1L), eq(2L)); - Map result = virtualMachineManagerImpl.getDiskOfferingSuitabilityForVm(1L, diskOfferingIds); - assertTrue(MapUtils.isNotEmpty(result)); - assertEquals(2, result.keySet().size()); - assertFalse(result.get(1L)); - assertTrue(result.get(2L)); + Map expected = Map.of(1L, false, 2L, true); + when(vmDiskOfferingSuitabilityService.getDiskOfferingSuitabilityForVm(vmInstanceVoMockId, diskOfferingIds)).thenReturn(expected); + + Map result = virtualMachineManagerImpl.getDiskOfferingSuitabilityForVm(vmInstanceVoMockId, diskOfferingIds); + + assertEquals(expected, result); + verify(vmDiskOfferingSuitabilityService).getDiskOfferingSuitabilityForVm(vmInstanceVoMockId, diskOfferingIds); } private void overrideVmMetadataConfigValue(final String manufacturer, final String product) { @@ -1377,73 +1214,29 @@ public void testUpdateVmMetadataManufacturerAndProductCustomManufacturer() { } @Test - public void recreateCheckpointsKvmOnVmAfterMigrationTestReturnIfNotKvm() { - Mockito.doReturn(HypervisorType.VMware).when(vmInstanceMock).getHypervisorType(); + public void recreateCheckpointsKvmOnVmAfterMigrationDelegatesToMigrationCheckpointService() { + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, hostMockId); - virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); - - verify(volumeDaoMock, never()).findByInstance(Mockito.anyLong()); + verify(vmMigrationCheckpointService).recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, hostMockId); } @Test - public void recreateCheckpointsKvmOnVmAfterMigrationTestReturnIfVolumesDoNotHaveCheckpoints() throws OperationTimedoutException, AgentUnavailableException { - Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); - Mockito.doReturn(new ArrayList()).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); + public void getVmVolumesWithCheckpointsToRecreateDelegatesToMigrationCheckpointService() { + List expected = List.of(new VolumeObjectTO()); + when(vmMigrationCheckpointService.getVmVolumesWithCheckpointsToRecreate(vmInstanceMock)).thenReturn(expected); - virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + List result = virtualMachineManagerImpl.getVmVolumesWithCheckpointsToRecreate(vmInstanceMock); - verify(agentManagerMock, never()).send(Mockito.anyLong(), (Command) any()); - } - - @Test (expected = CloudRuntimeException.class) - public void recreateCheckpointsKvmOnVmAfterMigrationTestAgentUnavailableThrowsCloudRuntimeExceptionAndEndsSnapshotChains() throws OperationTimedoutException, AgentUnavailableException { - Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); - Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); - - doThrow(new AgentUnavailableException(0)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); - Mockito.doNothing().when(snapshotManagerMock).endSnapshotChainForVolume(Mockito.anyLong(), Mockito.any()); - - virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); - - verify(snapshotManagerMock, Mockito.times(1)).endSnapshotChainForVolume(Mockito.anyLong(),any()); - } - - @Test (expected = CloudRuntimeException.class) - public void recreateCheckpointsKvmOnVmAfterMigrationTestOperationTimedoutExceptionThrowsCloudRuntimeExceptionAndEndsSnapshotChains() throws OperationTimedoutException, AgentUnavailableException { - Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); - Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); - - doThrow(new OperationTimedoutException(null, 0, 0, 0, false)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); - Mockito.doNothing().when(snapshotManagerMock).endSnapshotChainForVolume(Mockito.anyLong(), Mockito.any()); - - virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); - - verify(snapshotManagerMock, Mockito.times(1)).endSnapshotChainForVolume(Mockito.anyLong(),any()); + Assert.assertSame(expected, result); } @Test - public void recreateCheckpointsKvmOnVmAfterMigrationTestRecreationFails() throws OperationTimedoutException, AgentUnavailableException { - Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); - Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); - - Mockito.doReturn(new com.cloud.agent.api.Answer(null, false, null)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); - Mockito.doNothing().when(snapshotManagerMock).endSnapshotChainForVolume(Mockito.anyLong(), Mockito.any()); + public void endSnapshotChainForVolumesDelegatesToMigrationCheckpointService() { + Map volumeToPoolMap = new HashMap<>(); - virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + virtualMachineManagerImpl.endSnapshotChainForVolumes(volumeToPoolMap, HypervisorType.KVM); - verify(snapshotManagerMock, Mockito.times(1)).endSnapshotChainForVolume(Mockito.anyLong(),any()); - } - - @Test - public void recreateCheckpointsKvmOnVmAfterMigrationTestRecreationSucceeds() throws OperationTimedoutException, AgentUnavailableException { - Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); - Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); - - Mockito.doReturn(new com.cloud.agent.api.Answer(null, true, null)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); - - virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); - - verify(snapshotManagerMock, never()).endSnapshotChainForVolume(Mockito.anyLong(),any()); + verify(vmMigrationCheckpointService).endSnapshotChainForVolumes(volumeToPoolMap, HypervisorType.KVM); } @Test @@ -1709,14 +1502,6 @@ public void testPrepVMSpecForUnmanageInstance() { when(hostMock.getClusterId()).thenReturn(clusterMockId); - // Mock cpuOvercommitRatio and ramOvercommitRatio - ClusterDetailsVO cpuOvercommitRatio = Mockito.mock(ClusterDetailsVO.class); - when(cpuOvercommitRatio.getValue()).thenReturn("1.0"); - when(_clusterDetailsDao.findDetail(clusterMockId, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(cpuOvercommitRatio); - ClusterDetailsVO ramOvercommitRatio = Mockito.mock(ClusterDetailsVO.class); - when(ramOvercommitRatio.getValue()).thenReturn("1.0"); - when(_clusterDetailsDao.findDetail(clusterMockId, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(ramOvercommitRatio); - // Mock NICs List nics = new ArrayList<>(); NicVO nic1 = Mockito.mock(NicVO.class); @@ -1763,7 +1548,7 @@ public void testPrepVMSpecForUnmanageInstance() { // Assert assertNotNull(result); assertEquals(vmTO, result); - verify(_clusterDetailsDao, times(2)).findDetail(eq(clusterMockId), anyString()); + verify(vmStartProfilePreparationService).updateOverCommitRatioForVmProfile(any(VirtualMachineProfile.class), eq(clusterMockId)); verify(vmInstanceDetailsDao).listDetailsKeyPairs(anyLong(), anyList()); } diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmAllocationOrchestrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmAllocationOrchestrationServiceImplTest.java new file mode 100644 index 000000000000..0bad96a3c9bb --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmAllocationOrchestrationServiceImplTest.java @@ -0,0 +1,205 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VMTemplateZoneVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplateZoneDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.user.Account; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class VmAllocationOrchestrationServiceImplTest { + + @InjectMocks + private VmAllocationOrchestrationServiceImpl service; + + @Mock + private VolumeDao volsDao; + @Mock + private VMTemplateDao templateDao; + @Mock + private VMTemplateZoneDao templateZoneDao; + @Mock + private VolumeOrchestrationService volumeMgr; + + @Test + public void checkIfTemplateNeededForCreatingVmVolumes_existingRootVolumesReturnsWithoutTemplateLookup() { + long vmId = 1L; + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(volsDao.findReadyRootVolumesByInstance(vmId)).thenReturn(List.of(mock(VolumeVO.class))); + + service.checkIfTemplateNeededForCreatingVmVolumes(vm); + + verify(templateDao, never()).findById(any()); + } + + @Test + public void checkIfTemplateNeededForCreatingVmVolumes_missingTemplateThrows() { + long vmId = 1L; + long templateId = 2L; + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getTemplateId()).thenReturn(templateId); + when(volsDao.findReadyRootVolumesByInstance(vmId)).thenReturn(null); + when(templateDao.findById(templateId)).thenReturn(null); + + assertThrows(CloudRuntimeException.class, () -> service.checkIfTemplateNeededForCreatingVmVolumes(vm)); + } + + @Test + public void checkIfTemplateNeededForCreatingVmVolumes_missingZoneTemplateThrows() { + long vmId = 1L; + long templateId = 2L; + long dataCenterId = 3L; + VMInstanceVO vm = mock(VMInstanceVO.class); + VMTemplateVO template = mock(VMTemplateVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getTemplateId()).thenReturn(templateId); + when(vm.getDataCenterId()).thenReturn(dataCenterId); + when(volsDao.findReadyRootVolumesByInstance(vmId)).thenReturn(null); + when(templateDao.findById(templateId)).thenReturn(template); + when(template.getId()).thenReturn(templateId); + + assertThrows(CloudRuntimeException.class, () -> service.checkIfTemplateNeededForCreatingVmVolumes(vm)); + } + + @Test + public void checkIfTemplateNeededForCreatingVmVolumes_templateAvailableReturns() { + long vmId = 1L; + long templateId = 2L; + long dataCenterId = 3L; + VMInstanceVO vm = mock(VMInstanceVO.class); + VMTemplateVO template = mock(VMTemplateVO.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getTemplateId()).thenReturn(templateId); + when(vm.getDataCenterId()).thenReturn(dataCenterId); + when(volsDao.findReadyRootVolumesByInstance(vmId)).thenReturn(new ArrayList<>()); + when(templateDao.findById(templateId)).thenReturn(template); + when(template.getId()).thenReturn(templateId); + when(templateZoneDao.findByZoneTemplate(dataCenterId, templateId)).thenReturn(mock(VMTemplateZoneVO.class)); + + service.checkIfTemplateNeededForCreatingVmVolumes(vm); + } + + @Test + public void allocateRootVolume_isoTemplateAllocatesRawRootVolumeInVolumeContext() { + VMInstanceVO vm = mock(VMInstanceVO.class); + VirtualMachineTemplate template = mock(VirtualMachineTemplate.class); + DiskOfferingInfo rootDiskOfferingInfo = mock(DiskOfferingInfo.class); + DiskOffering diskOffering = mock(DiskOffering.class); + Account owner = mock(Account.class); + when(vm.getId()).thenReturn(11L); + when(template.getFormat()).thenReturn(ImageFormat.ISO); + when(rootDiskOfferingInfo.getDiskOffering()).thenReturn(diskOffering); + when(rootDiskOfferingInfo.getSize()).thenReturn(50L); + when(rootDiskOfferingInfo.getMinIops()).thenReturn(10L); + when(rootDiskOfferingInfo.getMaxIops()).thenReturn(20L); + + try (MockedStatic callContext = Mockito.mockStatic(CallContext.class)) { + CallContext currentContext = mock(CallContext.class); + callContext.when(CallContext::current).thenReturn(currentContext); + callContext.when(() -> CallContext.register(currentContext, ApiCommandResourceType.Volume)).thenReturn(mock(CallContext.class)); + + service.allocateRootVolume(vm, template, rootDiskOfferingInfo, owner, 100L, mock(Volume.class), mock(Snapshot.class)); + + verify(volumeMgr).allocateRawVolume(Volume.Type.ROOT, "ROOT-11", diskOffering, 50L, 10L, 20L, vm, template, owner, null, true); + callContext.verify(CallContext::unregister); + } + } + + @Test + public void allocateRootVolume_externalTemplateSkipsRootVolumeAllocation() { + VMInstanceVO vm = mock(VMInstanceVO.class); + VirtualMachineTemplate template = mock(VirtualMachineTemplate.class); + DiskOfferingInfo rootDiskOfferingInfo = mock(DiskOfferingInfo.class); + when(vm.getId()).thenReturn(11L); + when(template.getFormat()).thenReturn(ImageFormat.EXTERNAL); + + try (MockedStatic callContext = Mockito.mockStatic(CallContext.class)) { + CallContext currentContext = mock(CallContext.class); + callContext.when(CallContext::current).thenReturn(currentContext); + callContext.when(() -> CallContext.register(currentContext, ApiCommandResourceType.Volume)).thenReturn(mock(CallContext.class)); + + service.allocateRootVolume(vm, template, rootDiskOfferingInfo, mock(Account.class), 100L, mock(Volume.class), mock(Snapshot.class)); + + verify(volumeMgr, never()).allocateRawVolume(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), anyBoolean()); + verify(volumeMgr, never()).allocateTemplatedVolumes(any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any()); + callContext.verify(CallContext::unregister); + } + } + + @Test + public void allocateRootVolume_regularTemplateAllocatesTemplatedRootVolumeInVolumeContext() { + VMInstanceVO vm = mock(VMInstanceVO.class); + VirtualMachineTemplate template = mock(VirtualMachineTemplate.class); + DiskOfferingInfo rootDiskOfferingInfo = mock(DiskOfferingInfo.class); + DiskOffering diskOffering = mock(DiskOffering.class); + Account owner = mock(Account.class); + Volume volume = mock(Volume.class); + Snapshot snapshot = mock(Snapshot.class); + when(vm.getId()).thenReturn(11L); + when(template.getFormat()).thenReturn(ImageFormat.QCOW2); + when(rootDiskOfferingInfo.getDiskOffering()).thenReturn(diskOffering); + when(rootDiskOfferingInfo.getMinIops()).thenReturn(10L); + when(rootDiskOfferingInfo.getMaxIops()).thenReturn(20L); + + try (MockedStatic callContext = Mockito.mockStatic(CallContext.class)) { + CallContext currentContext = mock(CallContext.class); + callContext.when(CallContext::current).thenReturn(currentContext); + callContext.when(() -> CallContext.register(currentContext, ApiCommandResourceType.Volume)).thenReturn(mock(CallContext.class)); + + service.allocateRootVolume(vm, template, rootDiskOfferingInfo, owner, 100L, volume, snapshot); + + verify(volumeMgr).allocateTemplatedVolumes(Volume.Type.ROOT, "ROOT-11", diskOffering, 100L, 10L, 20L, template, vm, owner, volume, snapshot); + callContext.verify(CallContext::unregister); + } + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmCommandSpecPostProcessingServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmCommandSpecPostProcessingServiceImplTest.java new file mode 100644 index 000000000000..53b207cce8b4 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmCommandSpecPostProcessingServiceImplTest.java @@ -0,0 +1,295 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmCommandSpecPostProcessingServiceImplTest { + + @InjectMocks + private VmCommandSpecPostProcessingServiceImpl service; + + @Mock + private VolumeDao volumeDao; + @Mock + private VolumeOrchestrationService volumeMgr; + @Mock + private Host host; + + private static final long VOLUME_ID = 42L; + private static final String IQN = "iqn.2026-05.example:volume-42"; + private static final String ANSWER_PATH = "answer-path"; + private static final String CHAIN_INFO = "chain-info"; + private static final String DATASTORE_UUID = "datastore-uuid"; + + @Test + public void setEnterSetupMode_nullParams_setsFalse() { + VirtualMachineTO vmTo = newVmTo(); + + service.setEnterSetupMode(vmTo, null); + + assertFalse(vmTo.isEnterHardwareSetup()); + } + + @Test + public void setEnterSetupMode_trueParam_setsTrue() { + VirtualMachineTO vmTo = newVmTo(); + + service.setEnterSetupMode(vmTo, Map.of(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE)); + + assertTrue(vmTo.isEnterHardwareSetup()); + } + + @Test + public void addExtraConfig_copiesOnlyExtraConfigDetails() { + VirtualMachineTO vmTo = newVmTo(); + vmTo.setDetails(Map.of( + ApiConstants.EXTRA_CONFIG + "-0", "value-0", + ApiConstants.EXTRA_CONFIG + ".nested", "value-1", + "unrelated", "ignored")); + + service.addExtraConfig(vmTo); + + assertEquals(2, vmTo.getExtraConfig().size()); + assertEquals("value-0", vmTo.getExtraConfig().get(ApiConstants.EXTRA_CONFIG + "-0")); + assertEquals("value-1", vmTo.getExtraConfig().get(ApiConstants.EXTRA_CONFIG + ".nested")); + assertNull(vmTo.getExtraConfig().get("unrelated")); + } + + @Test + public void addExtraConfig_nullDetails_preservesExistingNullPointerBehavior() { + VirtualMachineTO vmTo = newVmTo(); + vmTo.setDetails(null); + + assertThrows(NullPointerException.class, () -> service.addExtraConfig(vmTo)); + } + + @Test + public void prepareManagedKvmDiskPath_setsDiskVolumeObjectAndVolumePathWhenMissing() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + VolumeVO volume = newVolume(IQN, null); + when(volumeDao.findById(VOLUME_ID)).thenReturn(volume); + + service.prepareManagedDiskPaths(new DiskTO[] { disk }, HypervisorType.KVM); + + assertEquals(IQN, disk.getPath()); + assertEquals(IQN, volumeObjectTO.getPath()); + assertEquals(IQN, volume.getPath()); + verify(volumeDao).update(VOLUME_ID, volume); + } + + @Test + public void prepareManagedKvmDiskPath_nonKvmNoops() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + + service.prepareManagedDiskPaths(new DiskTO[] { disk }, HypervisorType.VMware); + + assertNull(disk.getPath()); + assertNull(volumeObjectTO.getPath()); + verify(volumeDao, never()).findById(VOLUME_ID); + } + + @Test + public void prepareManagedKvmDiskPath_existingPathDoesNotPersist() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, "existing-path", true); + + service.prepareManagedDiskPaths(new DiskTO[] { disk }, HypervisorType.KVM); + + assertEquals("existing-path", disk.getPath()); + verify(volumeDao, never()).findById(VOLUME_ID); + } + + @Test + public void prepareManagedKvmDiskPath_unmanagedDiskNoops() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, false); + + service.prepareManagedDiskPaths(new DiskTO[] { disk }, HypervisorType.KVM); + + assertNull(disk.getPath()); + verify(volumeDao, never()).findById(VOLUME_ID); + } + + @Test + public void applyStartAnswerDiskMetadata_updatesPathAndImageFormatForMatchingIqn() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + VolumeVO volume = newVolume(IQN, null); + when(volumeDao.findById(VOLUME_ID)).thenReturn(volume); + + service.applyStartAnswerDiskMetadata(new DiskTO[] { disk }, + Map.of(IQN, Map.of(StartAnswer.PATH, ANSWER_PATH, StartAnswer.IMAGE_FORMAT, ImageFormat.QCOW2.name()))); + + assertEquals(ANSWER_PATH, volume.getPath()); + assertEquals(ImageFormat.QCOW2, volume.getFormat()); + verify(volumeDao).update(VOLUME_ID, volume); + } + + @Test + public void applyStartAnswerDiskMetadata_noIqnDataNoops() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + VolumeVO volume = newVolume(IQN, null); + when(volumeDao.findById(VOLUME_ID)).thenReturn(volume); + + service.applyStartAnswerDiskMetadata(new DiskTO[] { disk }, Map.of("other-iqn", Map.of(StartAnswer.PATH, ANSWER_PATH))); + + assertNull(volume.getPath()); + verify(volumeDao, never()).update(eq(VOLUME_ID), eq(volume)); + } + + @Test + public void applyStartAnswerDiskMetadata_nullIqnMapNoops() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + + service.applyStartAnswerDiskMetadata(new DiskTO[] { disk }, null); + + verify(volumeDao, never()).findById(VOLUME_ID); + } + + @Test + public void syncDiskChainChange_skipsIsoDisks() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ISO, null, true); + VirtualMachineTO vmTo = newVmTo(); + vmTo.setDisks(new DiskTO[] { disk }); + + service.syncDiskChainChange(newStartAnswer(vmTo)); + + verify(volumeDao, never()).findById(VOLUME_ID); + verify(volumeMgr, never()).updateVolumeDiskChain(eq(VOLUME_ID), eq(null), eq(CHAIN_INFO), eq(DATASTORE_UUID)); + } + + @Test + public void syncDiskChainChange_usesAnswerPathWhenPresent() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + volumeObjectTO.setPath(ANSWER_PATH); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + VirtualMachineTO vmTo = newVmTo(); + vmTo.setDisks(new DiskTO[] { disk }); + VolumeVO volume = newVolume(IQN, "persisted-path"); + when(volumeDao.findById(VOLUME_ID)).thenReturn(volume); + + service.syncDiskChainChange(newStartAnswer(vmTo)); + + verify(volumeMgr).updateVolumeDiskChain(VOLUME_ID, ANSWER_PATH, CHAIN_INFO, DATASTORE_UUID); + } + + @Test + public void syncDiskChainChange_usesPersistedVolumePathWhenAnswerPathMissing() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + VirtualMachineTO vmTo = newVmTo(); + vmTo.setDisks(new DiskTO[] { disk }); + VolumeVO volume = newVolume(IQN, "persisted-path"); + when(volumeDao.findById(VOLUME_ID)).thenReturn(volume); + + service.syncDiskChainChange(newStartAnswer(vmTo)); + + verify(volumeMgr).updateVolumeDiskChain(VOLUME_ID, "persisted-path", CHAIN_INFO, DATASTORE_UUID); + } + + @Test + public void syncDiskChainChange_deployAsIsPersistsVolumeObjectPath() { + VolumeObjectTO volumeObjectTO = newVolumeObjectTo(VOLUME_ID); + volumeObjectTO.setPath(ANSWER_PATH); + DiskTO disk = newDisk(volumeObjectTO, Volume.Type.ROOT, null, true); + VirtualMachineTO vmTo = newVmTo(); + vmTo.setDisks(new DiskTO[] { disk }); + vmTo.setDeployAsIsInfo(new com.cloud.agent.api.to.DeployAsIsInfoTO()); + VolumeVO volume = newVolume(IQN, "persisted-path"); + when(volumeDao.findById(VOLUME_ID)).thenReturn(volume); + + service.syncDiskChainChange(newStartAnswer(vmTo)); + + ArgumentCaptor volumeCaptor = ArgumentCaptor.forClass(VolumeVO.class); + verify(volumeDao).update(eq(VOLUME_ID), volumeCaptor.capture()); + assertEquals(ANSWER_PATH, volumeCaptor.getValue().getPath()); + verify(volumeMgr).updateVolumeDiskChain(VOLUME_ID, ANSWER_PATH, CHAIN_INFO, DATASTORE_UUID); + } + + private VirtualMachineTO newVmTo() { + VirtualMachineTO vmTo = new VirtualMachineTO(1L, "i-2-VM", VirtualMachine.Type.User, 1, 1000, 1024L, 1024L, null, "Other", false, false, null); + return vmTo; + } + + private VolumeObjectTO newVolumeObjectTo(long id) { + VolumeObjectTO volumeObjectTO = new VolumeObjectTO(); + volumeObjectTO.setId(id); + volumeObjectTO.setPath(null); + volumeObjectTO.setChainInfo(CHAIN_INFO); + volumeObjectTO.setUpdatedDataStoreUUID(DATASTORE_UUID); + return volumeObjectTO; + } + + private DiskTO newDisk(VolumeObjectTO volumeObjectTO, Volume.Type type, String path, boolean managed) { + DiskTO disk = new DiskTO(volumeObjectTO, 0L, path, type); + disk.setDetails(Map.of(DiskTO.MANAGED, Boolean.toString(managed))); + return disk; + } + + private VolumeVO newVolume(String iScsiName, String path) { + VolumeVO volume = org.mockito.Mockito.spy(new VolumeVO() {}); + when(volume.getId()).thenReturn(VOLUME_ID); + volume.set_iScsiName(iScsiName); + volume.setPath(path); + return volume; + } + + private StartAnswer newStartAnswer(VirtualMachineTO vmTo) { + when(host.getPrivateIpAddress()).thenReturn("192.0.2.10"); + StartCommand command = new StartCommand(vmTo, host, true); + return new StartAnswer(command); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmDestroyOrchestrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmDestroyOrchestrationServiceImplTest.java new file mode 100644 index 000000000000..3834dc67ea2a --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmDestroyOrchestrationServiceImplTest.java @@ -0,0 +1,194 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.gpu.GpuService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.CheckVirtualMachineAnswer; +import com.cloud.agent.api.CheckVirtualMachineCommand; +import com.cloud.agent.api.RestoreVMSnapshotAnswer; +import com.cloud.agent.api.RestoreVMSnapshotCommand; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackWithException; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotManager; +import com.cloud.vm.snapshot.dao.VMSnapshotDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmDestroyOrchestrationServiceImplTest { + + private static final String VM_UUID = "vm-uuid"; + private static final long VM_ID = 7L; + private static final long HOST_ID = 42L; + private static final String INSTANCE_NAME = "i-2-7-VM"; + + @InjectMocks + private VmDestroyOrchestrationServiceImpl service; + + @Mock + private VMInstanceDao vmDao; + @Mock + private UserVmDao userVmDao; + @Mock + private VMSnapshotDao vmSnapshotDao; + @Mock + private VMSnapshotManager vmSnapshotMgr; + @Mock + private AgentManager agentMgr; + @Mock + private GpuService gpuService; + @Mock + private BackupManager backupManager; + @Mock + private VirtualMachineManager virtualMachineManager; + @Mock + private VMInstanceVO vm; + @Mock + private UserVmVO userVm; + + @Test + public void destroy_missingVm_doesNothing() throws Exception { + service.destroy(VM_UUID, true); + + verify(virtualMachineManager, never()).advanceStop(eq(VM_UUID), anyBoolean()); + verify(gpuService, never()).deallocateAllGpuDevicesForVm(VM_ID); + } + + @Test + public void destroy_expungeTrue_stopsDeletesSnapshotsDeallocatesGpuAndTransitions() throws Exception { + prepareDestroyableVm(HypervisorType.KVM); + when(vmSnapshotMgr.deleteAllVMSnapshots(VM_ID, null)).thenReturn(true); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.DestroyRequested, HOST_ID)).thenReturn(true); + when(virtualMachineManager.stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, HOST_ID)).thenReturn(true); + + try (MockedStatic transaction = Mockito.mockStatic(Transaction.class)) { + transaction.when(() -> Transaction.execute(Mockito.>any())) + .thenAnswer(invocation -> { + TransactionCallbackWithException callback = invocation.getArgument(0); + return callback.doInTransaction(null); + }); + + service.destroy(VM_UUID, true); + } + + verify(virtualMachineManager).advanceStop(VM_UUID, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + verify(vmSnapshotMgr).deleteAllVMSnapshots(VM_ID, null); + verify(gpuService).deallocateAllGpuDevicesForVm(VM_ID); + verify(backupManager).checkAndRemoveBackupOfferingBeforeExpunge(vm); + verify(virtualMachineManager).stateTransitTo(vm, VirtualMachine.Event.DestroyRequested, HOST_ID); + verify(virtualMachineManager).stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, HOST_ID); + } + + @Test + public void destroy_unexpectedResourceUnavailableFromStop_wrapsAsCloudRuntimeException() throws Exception { + prepareDestroyableVm(HypervisorType.KVM); + ResourceUnavailableException unavailable = new ResourceUnavailableException("stop failed", VirtualMachine.class, VM_ID); + Mockito.doThrow(unavailable).when(virtualMachineManager).advanceStop(VM_UUID, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.destroy(VM_UUID, false)); + + assertTrue(exception.getMessage().contains("Unable to stop vm " + VM_UUID)); + } + + @Test + public void deleteVMSnapshots_nonVmwareFailure_throwsExistingMessage() { + prepareVm(HypervisorType.KVM); + when(vm.toString()).thenReturn("vm-for-snapshot-delete"); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.deleteVMSnapshots(vm, false)); + + assertTrue(exception.getMessage().contains("Unable to delete Instance Snapshots for vm-for-snapshot-delete")); + } + + @Test + public void deleteVMSnapshots_vmwareExpunge_deletesOnlyDatabaseRows() { + prepareVm(HypervisorType.VMware); + + service.deleteVMSnapshots(vm, true); + + verify(vmSnapshotMgr, never()).deleteAllVMSnapshots(VM_ID, null); + verify(vmSnapshotMgr).deleteVMSnapshotsFromDB(VM_ID, false); + } + + @Test + public void checkVmOnHost_powerOffAnswer_returnsFalse() throws Exception { + prepareVm(HypervisorType.KVM); + CheckVirtualMachineCommand command = new CheckVirtualMachineCommand(INSTANCE_NAME); + when(agentMgr.send(eq(HOST_ID), any(CheckVirtualMachineCommand.class))) + .thenReturn(new CheckVirtualMachineAnswer(command, PowerState.PowerOff, null)); + + assertFalse(service.checkVmOnHost(vm, HOST_ID)); + + verify(userVmDao, never()).findById(VM_ID); + } + + @Test + public void checkVmOnHost_restoreCommandFailureStillReturnsTrue() throws Exception { + prepareVm(HypervisorType.KVM); + CheckVirtualMachineCommand checkCommand = new CheckVirtualMachineCommand(INSTANCE_NAME); + RestoreVMSnapshotCommand restoreCommand = Mockito.mock(RestoreVMSnapshotCommand.class); + when(agentMgr.send(eq(HOST_ID), any(CheckVirtualMachineCommand.class))) + .thenReturn(new CheckVirtualMachineAnswer(checkCommand, PowerState.PowerOn, null)); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + when(vmSnapshotDao.findByVm(VM_ID)).thenReturn(Collections.emptyList()); + when(vmSnapshotMgr.createRestoreCommand(userVm, Collections.emptyList())).thenReturn(restoreCommand); + when(agentMgr.send(HOST_ID, restoreCommand)).thenReturn(new RestoreVMSnapshotAnswer(restoreCommand, false, "restore failed")); + + assertTrue(service.checkVmOnHost(vm, HOST_ID)); + } + + private void prepareDestroyableVm(HypervisorType hypervisorType) { + prepareVm(hypervisorType); + when(vm.getState()).thenReturn(State.Running); + when(vm.getHostId()).thenReturn(HOST_ID); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + } + + private void prepareVm(HypervisorType hypervisorType) { + when(vm.getId()).thenReturn(VM_ID); + when(vm.getInstanceName()).thenReturn(INSTANCE_NAME); + when(vm.getHypervisorType()).thenReturn(hypervisorType); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmDiskOfferingSuitabilityServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmDiskOfferingSuitabilityServiceImplTest.java new file mode 100644 index 000000000000..236e6ce3b3d6 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmDiskOfferingSuitabilityServiceImplTest.java @@ -0,0 +1,309 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmDiskOfferingSuitabilityServiceImplTest { + + @InjectMocks + private VmDiskOfferingSuitabilityServiceImpl service; + + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private VMInstanceDetailsDao vmInstanceDetailsDao; + @Mock + private VolumeDao volumeDao; + @Mock + private PrimaryDataStoreDao storagePoolDao; + @Mock + private HostDao hostDao; + @Mock + private ClusterDao clusterDao; + @Mock + private DiskOfferingDao diskOfferingDao; + @Mock + private StoragePoolAllocator storagePoolAllocator; + + @Mock + private VMInstanceVO vm; + @Mock + private VirtualMachineProfile profile; + @Mock + private DiskOfferingVO diskOffering; + @Mock + private StoragePool storagePool; + @Mock + private HostVO host; + @Mock + private HostVO lastHost; + @Mock + private VolumeVO volume; + @Mock + private StoragePoolVO storagePoolVo; + + private final long vmId = 1L; + private final long zoneId = 2L; + private final long podId = 3L; + private final long accountId = 4L; + private final long domainId = 5L; + private final long currentHostId = 6L; + private final long lastHostId = 7L; + private final long clusterId = 8L; + private final long lastHostClusterId = 9L; + private final long poolId = 10L; + private final long diskOfferingId = 11L; + + @Before + public void setUp() { + service.setStoragePoolAllocators(List.of(storagePoolAllocator)); + when(vm.getId()).thenReturn(vmId); + } + + @Test + public void isDiskOfferingSuitableForVmReturnsTrueWhenAllocatorFindsPool() { + configureVmForDiskSuitability(); + when(diskOfferingDao.findById(diskOfferingId)).thenReturn(diskOffering); + when(storagePool.getName()).thenReturn("pool"); + when(storagePoolAllocator.allocateToPool(any(DiskProfile.class), eq(profile), any(DeploymentPlan.class), any(ExcludeList.class), eq(1))) + .thenReturn(List.of(storagePool)); + + boolean result = service.isDiskOfferingSuitableForVm(vm, profile, podId, clusterId, currentHostId, diskOfferingId); + + assertTrue(result); + ArgumentCaptor planCaptor = ArgumentCaptor.forClass(DeploymentPlan.class); + verify(storagePoolAllocator).allocateToPool(any(DiskProfile.class), eq(profile), planCaptor.capture(), any(ExcludeList.class), eq(1)); + DeploymentPlan plan = planCaptor.getValue(); + assertEquals(zoneId, plan.getDataCenterId()); + assertEquals(podId, plan.getPodId().longValue()); + assertEquals(clusterId, plan.getClusterId().longValue()); + assertEquals(currentHostId, plan.getHostId().longValue()); + } + + @Test + public void isDiskOfferingSuitableForVmReturnsFalseWhenAllocatorReturnsEmpty() { + configureVmForDiskSuitability(); + when(diskOfferingDao.findById(diskOfferingId)).thenReturn(diskOffering); + when(storagePoolAllocator.allocateToPool(any(DiskProfile.class), eq(profile), any(DeploymentPlan.class), any(ExcludeList.class), eq(1))) + .thenReturn(new ArrayList<>()); + + assertFalse(service.isDiskOfferingSuitableForVm(vm, profile, podId, clusterId, currentHostId, diskOfferingId)); + } + + @Test + public void getDiskOfferingSuitabilityForVmReturnsEmptyWhenDeployVmDetailExists() { + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(vmInstanceDetailsDao.findDetail(vmId, VmDetailConstants.DEPLOY_VM)).thenReturn(new VMInstanceDetailVO()); + + Map result = service.getDiskOfferingSuitabilityForVm(vmId, List.of(1L, 2L)); + + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(hostDao, never()).findById(anyLong()); + verify(storagePoolAllocator, never()).allocateToPool(any(), any(), any(), any(), anyInt()); + } + + @Test + public void getDiskOfferingSuitabilityForVmChecksEachOfferingWithResolvedClusterAndHost() { + configureVmForDiskSuitability(); + when(vmInstanceDao.findById(vmId)).thenReturn(vm); + when(vm.getHostId()).thenReturn(currentHostId); + when(hostDao.findById(currentHostId)).thenReturn(host); + when(host.getClusterId()).thenReturn(clusterId); + ClusterVO cluster = Mockito.mock(ClusterVO.class); + when(cluster.getPodId()).thenReturn(podId); + when(clusterDao.findById(clusterId)).thenReturn(cluster); + DiskOfferingVO firstOffering = Mockito.mock(DiskOfferingVO.class); + DiskOfferingVO secondOffering = Mockito.mock(DiskOfferingVO.class); + when(diskOfferingDao.findById(1L)).thenReturn(firstOffering); + when(diskOfferingDao.findById(2L)).thenReturn(secondOffering); + when(storagePool.getName()).thenReturn("pool"); + when(storagePoolAllocator.allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), any(ExcludeList.class), eq(1))) + .thenReturn(new ArrayList<>()) + .thenReturn(List.of(storagePool)); + + Map result = service.getDiskOfferingSuitabilityForVm(vmId, List.of(1L, 2L)); + + assertEquals(2, result.size()); + assertFalse(result.get(1L)); + assertTrue(result.get(2L)); + verify(diskOfferingDao).findById(1L); + verify(diskOfferingDao).findById(2L); + } + + @Test + public void findClusterAndHostIdForVmUsesCurrentHostWhenAllowed() { + when(vm.getHostId()).thenReturn(currentHostId); + when(hostDao.findById(currentHostId)).thenReturn(host); + when(host.getClusterId()).thenReturn(clusterId); + + Pair result = service.findClusterAndHostIdForVm(vm, false); + + assertEquals(clusterId, result.first().longValue()); + assertEquals(currentHostId, result.second().longValue()); + } + + @Test + public void findClusterAndHostIdForVmSkipsCurrentHostForStartingVmWhenRequested() { + when(vm.getState()).thenReturn(State.Starting); + when(vm.getLastHostId()).thenReturn(lastHostId); + when(hostDao.findById(lastHostId)).thenReturn(lastHost); + when(lastHost.getClusterId()).thenReturn(lastHostClusterId); + + Pair result = service.findClusterAndHostIdForVm(vm, true); + + assertEquals(lastHostClusterId, result.first().longValue()); + assertEquals(lastHostId, result.second().longValue()); + verify(hostDao, never()).findById(currentHostId); + } + + @Test + public void findClusterAndHostIdForVmUsesLastHostWhenCurrentHostMissing() { + when(vm.getHostId()).thenReturn(null); + when(vm.getLastHostId()).thenReturn(lastHostId); + when(hostDao.findById(lastHostId)).thenReturn(lastHost); + when(lastHost.getClusterId()).thenReturn(lastHostClusterId); + + Pair result = service.findClusterAndHostIdForVm(vm, false); + + assertEquals(lastHostClusterId, result.first().longValue()); + assertEquals(lastHostId, result.second().longValue()); + } + + @Test + public void findClusterAndHostIdForVmFallsBackToReadyVolumePoolClusterAndHost() { + configureReadyVolumeWithCluster(); + when(hostDao.findHypervisorHostInCluster(clusterId)).thenReturn(List.of(host)); + when(host.getId()).thenReturn(currentHostId); + + Pair result = service.findClusterAndHostIdForVm(vm, false); + + assertEquals(clusterId, result.first().longValue()); + assertEquals(currentHostId, result.second().longValue()); + } + + @Test + public void findClusterAndHostIdForVmFromIdReturnsNullPairWhenVmMissing() { + when(vmInstanceDao.findById(vmId)).thenReturn(null); + + Pair result = service.findClusterAndHostIdForVm(vmId); + + assertNull(result.first()); + assertNull(result.second()); + } + + @Test + public void findClusterAndHostIdForVmFromVolumesIgnoresNonReadyVolumes() { + when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); + when(volume.getState()).thenReturn(Volume.State.Allocated); + + Pair result = service.findClusterAndHostIdForVmFromVolumes(vmId); + + assertNull(result.first()); + assertNull(result.second()); + verify(storagePoolDao, never()).findById(anyLong()); + } + + @Test + public void findClusterAndHostIdForVmFromVolumesIgnoresPoolsWithoutCluster() { + when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); + when(volume.getState()).thenReturn(Volume.State.Ready); + when(volume.getPoolId()).thenReturn(poolId); + when(storagePoolDao.findById(poolId)).thenReturn(storagePoolVo); + when(storagePoolVo.getClusterId()).thenReturn(null); + + Pair result = service.findClusterAndHostIdForVmFromVolumes(vmId); + + assertNull(result.first()); + assertNull(result.second()); + verify(hostDao, never()).findHypervisorHostInCluster(anyLong()); + } + + @Test + public void findClusterAndHostIdForVmFromVolumesKeepsClusterWhenNoHostFound() { + configureReadyVolumeWithCluster(); + when(hostDao.findHypervisorHostInCluster(clusterId)).thenReturn(new ArrayList<>()); + + Pair result = service.findClusterAndHostIdForVmFromVolumes(vmId); + + assertEquals(clusterId, result.first().longValue()); + assertNull(result.second()); + } + + private void configureVmForDiskSuitability() { + when(vm.getDataCenterId()).thenReturn(zoneId); + when(vm.getAccountId()).thenReturn(accountId); + when(vm.getDomainId()).thenReturn(domainId); + when(profile.getHypervisorType()).thenReturn(HypervisorType.KVM); + } + + private void configureReadyVolumeWithCluster() { + when(volumeDao.findByInstance(vmId)).thenReturn(List.of(volume)); + when(volume.getState()).thenReturn(Volume.State.Ready); + when(volume.getPoolId()).thenReturn(poolId); + when(storagePoolDao.findById(poolId)).thenReturn(storagePoolVo); + when(storagePoolVo.getClusterId()).thenReturn(clusterId); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmExpungeCommandServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmExpungeCommandServiceImplTest.java new file mode 100644 index 000000000000..2e172c5e87a0 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmExpungeCommandServiceImplTest.java @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.manager.Commands; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class VmExpungeCommandServiceImplTest { + + private static final long HOST_ID = 42L; + private static final String VM_STRING = "vm-to-expunge"; + + @InjectMocks + private VmExpungeCommandServiceImpl service; + + @Mock + private AgentManager agentMgr; + + @Mock + private VMInstanceVO vm; + + @Test + public void sendVolumeExpungeCommands_emptyCommands_doesNotSend() throws Exception { + service.sendVolumeExpungeCommands(Collections.emptyList(), HOST_ID, vm); + + verify(agentMgr, never()).send(anyLong(), any(Commands.class)); + } + + @Test + public void sendVolumeExpungeCommands_nullHost_doesNotSend() throws Exception { + service.sendVolumeExpungeCommands(List.of(new TestCommand()), null, vm); + + verify(agentMgr, never()).send(anyLong(), any(Commands.class)); + } + + @Test + public void sendVolumeExpungeCommands_consoleProxy_setsBypassAndSends() throws Exception { + TestCommand command = new TestCommand(); + when(vm.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy); + answerSuccessfully(); + + service.sendVolumeExpungeCommands(List.of(command), HOST_ID, vm); + + Commands sentCommands = captureSentCommands(); + assertEquals(1, sentCommands.size()); + assertSame(command, sentCommands.toCommands()[0]); + assertTrue(command.isBypassHostMaintenance()); + } + + @Test + public void sendVolumeExpungeCommands_failedAnswer_throwsExistingBracketedMessage() throws Exception { + TestCommand command = new TestCommand(); + when(vm.toString()).thenReturn(VM_STRING); + answerWithFailure("storage cleanup failed"); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + () -> service.sendVolumeExpungeCommands(List.of(command), HOST_ID, vm)); + + assertEquals("Unable to expunge " + VM_STRING + " due to [storage cleanup failed].", exception.getMessage()); + } + + @Test + public void sendFinalizeExpungeCommands_combinesFinalizeThenNicCommandsAndSetsBypass() throws Exception { + TestCommand finalizeCommand = new TestCommand(); + TestCommand nicCommand = new TestCommand(); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + answerSuccessfully(); + + service.sendFinalizeExpungeCommands(List.of(finalizeCommand), List.of(nicCommand), vm, HOST_ID); + + Commands sentCommands = captureSentCommands(); + assertEquals(2, sentCommands.size()); + assertSame(finalizeCommand, sentCommands.toCommands()[0]); + assertSame(nicCommand, sentCommands.toCommands()[1]); + assertFalse(finalizeCommand.isBypassHostMaintenance()); + assertFalse(nicCommand.isBypassHostMaintenance()); + } + + @Test + public void sendFinalizeExpungeCommands_failedAnswer_throwsExistingUnbracketedMessage() throws Exception { + TestCommand command = new TestCommand(); + when(vm.toString()).thenReturn(VM_STRING); + answerWithFailure("finalize failed"); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + () -> service.sendFinalizeExpungeCommands(List.of(command), Collections.emptyList(), vm, HOST_ID)); + + assertEquals("Unable to expunge " + VM_STRING + " due to finalize failed", exception.getMessage()); + } + + private void answerSuccessfully() throws Exception { + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenAnswer(invocation -> { + Commands commands = invocation.getArgument(1); + Command[] sentCommands = commands.toCommands(); + Answer[] answers = new Answer[sentCommands.length]; + for (int i = 0; i < sentCommands.length; i++) { + answers[i] = new Answer(sentCommands[i]); + } + commands.setAnswers(answers); + return answers; + }); + } + + private void answerWithFailure(String details) throws Exception { + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenAnswer(invocation -> { + Commands commands = invocation.getArgument(1); + Command command = commands.toCommands()[0]; + Answer[] answers = new Answer[] {new Answer(command, false, details)}; + commands.setAnswers(answers); + return answers; + }); + } + + private Commands captureSentCommands() throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(Commands.class); + verify(agentMgr).send(eq(HOST_ID), captor.capture()); + return captor.getValue(); + } + + private static class TestCommand extends Command { + @Override + public boolean executeInSequence() { + return false; + } + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmExpungeOrchestrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmExpungeOrchestrationServiceImplTest.java new file mode 100644 index 000000000000..dad135cedf16 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmExpungeOrchestrationServiceImplTest.java @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.resource.ResourceCleanupService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.Command; +import com.cloud.deployasis.dao.UserVmDeployAsIsDetailsDao; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmExpungeOrchestrationServiceImplTest { + + private static final String VM_UUID = "vm-uuid"; + private static final long VM_ID = 7L; + private static final long HOST_ID = 42L; + + @InjectMocks + private VmExpungeOrchestrationServiceImpl service; + + @Mock + private VMInstanceDao vmDao; + @Mock + private UserVmDao userVmDao; + @Mock + private HypervisorGuruManager hvGuruMgr; + @Mock + private NetworkOrchestrationService networkMgr; + @Mock + private VolumeOrchestrationService volumeMgr; + @Mock + private VmExpungeCommandService vmExpungeCommandService; + @Mock + private UserVmDeployAsIsDetailsDao userVmDeployAsIsDetailsDao; + @Mock + private AnnotationDao annotationDao; + @Mock + private ResourceCleanupService resourceCleanupService; + @Mock + private VmIscsiTargetManager vmIscsiTargetManager; + @Mock + private VirtualMachineManager virtualMachineManager; + @Mock + private VmStateMachineActions vmStateMachineActions; + @Mock + private VMInstanceVO vm; + @Mock + private UserVmVO userVm; + @Mock + private HypervisorGuru hvGuru; + @Mock + private VirtualMachineGuru vmGuru; + + @Test + public void advanceExpunge_missingVmDoesNothing() throws Exception { + service.advanceExpunge((VMInstanceVO)null); + + verifyNoInteractions(virtualMachineManager, hvGuruMgr, networkMgr, volumeMgr, vmExpungeCommandService); + } + + @Test + public void isVmDestroyed_returnsTrueForRemovedVm() { + when(vm.getRemoved()).thenReturn(new java.util.Date()); + + assertTrue(service.isVmDestroyed(vm)); + } + + @Test + public void isVmDestroyed_returnsFalseForActiveVm() { + assertFalse(service.isVmDestroyed(vm)); + } + + @Test + public void advanceExpunge_externalVmMarksDetailBeforeStop() throws Exception { + prepareVm(HypervisorType.External, HOST_ID); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + prepareSuccessfulExpunge(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + service.advanceExpunge(vm); + + InOrder order = inOrder(userVmDao, userVm, virtualMachineManager); + order.verify(userVmDao).loadDetails(userVm); + order.verify(userVm).setDetail(VmDetailConstants.EXPUNGE_EXTERNAL_VM, Boolean.TRUE.toString()); + order.verify(userVmDao).saveDetails(userVm); + order.verify(virtualMachineManager).advanceStop(VM_UUID, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + } + + @Test + public void advanceExpunge_cleansResourcesAndSendsExpungeCommands() throws Exception { + prepareVm(HypervisorType.KVM, HOST_ID); + Command nicCommand = Mockito.mock(Command.class); + Command volumeCommand = Mockito.mock(Command.class); + Command finalizeCommand = Mockito.mock(Command.class); + List> targets = List.of(Map.of("iqn", "iqn.2026-05.test")); + prepareSuccessfulExpunge(List.of(nicCommand), List.of(volumeCommand), List.of(finalizeCommand), targets); + + service.advanceExpunge(vm); + + verify(virtualMachineManager).advanceStop(VM_UUID, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + verify(vmStateMachineActions).stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, HOST_ID); + verify(networkMgr).cleanupNics(any(VirtualMachineProfile.class)); + verify(vmExpungeCommandService).sendVolumeExpungeCommands(List.of(volumeCommand), HOST_ID, vm); + verify(volumeMgr).revokeAccess(VM_ID, HOST_ID); + verify(volumeMgr).cleanupVolumes(VM_ID); + verify(vmIscsiTargetManager).removeDynamicTargets(HOST_ID, targets); + verify(vmGuru).finalizeExpunge(vm); + verify(userVmDeployAsIsDetailsDao).removeDetails(VM_ID); + verify(annotationDao).removeByEntityType(AnnotationService.EntityType.VM.name(), VM_UUID); + verify(vmExpungeCommandService).sendFinalizeExpungeCommands(List.of(finalizeCommand), List.of(nicCommand), vm, HOST_ID); + verify(resourceCleanupService).purgeExpungedVmResourcesLaterIfNeeded(vm); + } + + @Test + public void advanceExpunge_noHostCleansVolumesWithoutRevokingAccessOrRemovingTargets() throws Exception { + prepareVm(HypervisorType.KVM, null); + prepareSuccessfulExpunge(Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + service.advanceExpunge(vm); + + verify(volumeMgr, never()).revokeAccess(eq(VM_ID), anyLong()); + verify(volumeMgr).cleanupVolumes(VM_ID); + verify(vmIscsiTargetManager, never()).removeDynamicTargets(anyLong(), any()); + } + + @Test + public void expunge_concurrentOperation_wrapsExistingMessage() throws Exception { + prepareVm(HypervisorType.KVM, HOST_ID); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + doThrow(new ConcurrentOperationException("busy")) + .when(virtualMachineManager).advanceStop(VM_UUID, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.expunge(VM_UUID)); + + assertEquals("Concurrent operation ", exception.getMessage()); + } + + @Test + public void expunge_operationTimedOut_wrapsExistingMessage() throws Exception { + prepareVm(HypervisorType.KVM, HOST_ID); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + doThrow(new OperationTimedoutException(null, HOST_ID, 1L, 1, false)) + .when(virtualMachineManager).advanceStop(VM_UUID, VirtualMachineManagerImpl.VmDestroyForcestop.value()); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.expunge(VM_UUID)); + + assertEquals("Operation timed out", exception.getMessage()); + } + + private void prepareVm(HypervisorType hypervisorType, Long hostId) { + when(vm.getId()).thenReturn(VM_ID); + when(vm.getUuid()).thenReturn(VM_UUID); + when(vm.getHypervisorType()).thenReturn(hypervisorType); + when(vm.getHostId()).thenReturn(hostId); + if (hostId == null) { + when(vm.getLastHostId()).thenReturn(null); + } + } + + private void prepareSuccessfulExpunge(List nicCommands, List volumeCommands, + List finalizeCommands, List> targets) throws Exception { + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vmStateMachineActions.stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, vm.getHostId())).thenReturn(true); + when(hvGuruMgr.getGuru(vm.getHypervisorType())).thenReturn(hvGuru); + when(hvGuru.finalizeExpungeNics(eq(vm), any())).thenReturn(nicCommands); + when(hvGuru.finalizeExpungeVolumes(vm)).thenReturn(volumeCommands); + when(vmIscsiTargetManager.getTargets(vm.getHostId(), VM_ID)).thenReturn(targets); + when(vmStateMachineActions.getVmGuru(vm)).thenReturn(vmGuru); + when(hvGuru.finalizeExpunge(vm)).thenReturn(finalizeCommands); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmExternalProvisioningManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmExternalProvisioningManagerImplTest.java new file mode 100644 index 000000000000..64e103db7ec4 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmExternalProvisioningManagerImplTest.java @@ -0,0 +1,490 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao; +import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.dc.DataCenter; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.UserVmDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmExternalProvisioningManagerImplTest { + + @Mock private AgentManager agentMgr; + @Mock private NicDao nicsDao; + @Mock private UserVmDao userVmDao; + @Mock private ExtensionsManager extensionsManager; + @Mock private ExtensionDetailsDao extensionDetailsDao; + @Mock private NetworkService networkService; + @Mock private HostDao hostDao; + @Mock private NetworkModel networkModel; + @Mock private HypervisorGuruManager hvGuruMgr; + + @InjectMocks + private VmExternalProvisioningManagerImpl manager; + + private static final long VM_ID = 42L; + private static final long HOST_ID = 17L; + private static final long ZONE_ID = 7L; + + private VirtualMachineTO mockVmTO(long id) { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + lenient().when(vmTO.getId()).thenReturn(id); + return vmTO; + } + + private Host mockHost(HypervisorType type) { + Host host = mock(Host.class); + lenient().when(host.getHypervisorType()).thenReturn(type); + lenient().when(host.getId()).thenReturn(HOST_ID); + return host; + } + + // ---- updateVmMetadataManufacturerAndProduct ---- + + @Test + public void updateVmMetadataUsesHypervisorBasedDefaultProductWhenConfigBlank() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getDataCenterId()).thenReturn(ZONE_ID); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + + manager.updateVmMetadataManufacturerAndProduct(vmTO, vm); + + verify(vmTO).setMetadataManufacturer(VirtualMachineManager.VmMetadataManufacturer.defaultValue()); + verify(vmTO).setMetadataProductName("CloudStack KVM Hypervisor"); + } + + // ---- updateExternalVmDetailsFromPrepareAnswer ---- + + @Test + public void updateExternalVmDetailsNoOpsWhenNewDetailsNull() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + UserVmVO userVm = mock(UserVmVO.class); + + manager.updateExternalVmDetailsFromPrepareAnswer(vmTO, userVm, null); + + verify(userVmDao, never()).saveDetails(any()); + verify(vmTO, never()).setDetails(anyMap()); + } + + @Test + public void updateExternalVmDetailsNoOpsWhenNewDetailsEqualsCurrent() { + Map existing = new HashMap<>(); + existing.put("k", "v"); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getDetails()).thenReturn(existing); + UserVmVO userVm = mock(UserVmVO.class); + + manager.updateExternalVmDetailsFromPrepareAnswer(vmTO, userVm, new HashMap<>(existing)); + + verify(userVmDao, never()).saveDetails(any()); + } + + @Test + public void updateExternalVmDetailsPersistsWhenDifferent() { + Map existing = new HashMap<>(); + existing.put("k", "v"); + Map updated = new HashMap<>(); + updated.put("k", "v2"); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getDetails()).thenReturn(existing); + UserVmVO userVm = mock(UserVmVO.class); + + manager.updateExternalVmDetailsFromPrepareAnswer(vmTO, userVm, updated); + + verify(vmTO).setDetails(updated); + verify(userVm).setDetails(updated); + verify(userVmDao).saveDetails(userVm); + } + + // ---- updateExternalVmDataFromPrepareAnswer ---- + + @Test + public void updateExternalVmDataNoOpsWhenNeitherChanged() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(updatedTO.getVncPassword()).thenReturn(null); + when(updatedTO.getDetails()).thenReturn(null); + + manager.updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); + + verify(userVmDao, never()).findById(anyLong()); + } + + @Test + public void updateExternalVmDataNoOpsWhenUserVmNotFound() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(updatedTO.getVncPassword()).thenReturn("newpass"); + when(vmTO.getVncPassword()).thenReturn("oldpass"); + when(userVmDao.findById(VM_ID)).thenReturn(null); + + manager.updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); + + verify(vmTO, never()).setVncPassword(any()); + } + + @Test + public void updateExternalVmDataAppliesNewVncPassword() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getVncPassword()).thenReturn("oldpass"); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(updatedTO.getVncPassword()).thenReturn("newpass"); + when(updatedTO.getDetails()).thenReturn(null); + UserVmVO userVm = mock(UserVmVO.class); + when(userVm.getPassword()).thenReturn("currentvm"); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + + manager.updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); + + verify(userVm).setVncPassword("newpass"); + verify(vmTO).setVncPassword("newpass"); + } + + // ---- updateExternalVmNicsFromPrepareAnswer ---- + + @Test + public void updateExternalVmNicsNoOpsWhenOriginalNicsNull() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(null); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(updatedTO.getNics()).thenReturn(new NicTO[]{mock(NicTO.class)}); + + manager.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(nicsDao, never()).update(anyLong(), any()); + } + + @Test + public void updateExternalVmNicsUpdatesNicWhenIpAndMacDiffer() { + NicTO original = mock(NicTO.class); + when(original.getNicUuid()).thenReturn("uuid-1"); + when(original.getMac()).thenReturn("aa:bb:cc:dd:ee:01"); + lenient().when(original.getIp()).thenReturn("10.0.0.1"); + lenient().when(original.getIp6Address()).thenReturn(null); + NicTO updated = mock(NicTO.class); + when(updated.getNicUuid()).thenReturn("uuid-1"); + when(updated.getMac()).thenReturn("aa:bb:cc:dd:ee:02"); + when(updated.getIp()).thenReturn("10.0.0.2"); + when(updated.getIp6Address()).thenReturn(null); + + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(new NicTO[]{original}); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(updatedTO.getNics()).thenReturn(new NicTO[]{updated}); + + NicVO nicVO = mock(NicVO.class); + when(nicVO.getId()).thenReturn(100L); + when(nicVO.getIPv4Address()).thenReturn("10.0.0.1"); + when(nicVO.getMacAddress()).thenReturn("aa:bb:cc:dd:ee:01"); + when(nicsDao.findByUuid("uuid-1")).thenReturn(nicVO); + + manager.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(nicVO).setIPv4Address("10.0.0.2"); + verify(nicVO).setMacAddress("aa:bb:cc:dd:ee:02"); + verify(nicsDao).update(eq(100L), eq(nicVO)); + } + + @Test + public void updateExternalVmNicsSkipsWhenNicNotFound() { + NicTO original = mock(NicTO.class); + when(original.getNicUuid()).thenReturn("uuid-1"); + when(original.getMac()).thenReturn("aa:bb:cc:dd:ee:01"); + lenient().when(original.getIp()).thenReturn("10.0.0.1"); + lenient().when(original.getIp6Address()).thenReturn(null); + NicTO updated = mock(NicTO.class); + when(updated.getNicUuid()).thenReturn("uuid-1"); + when(updated.getMac()).thenReturn("aa:bb:cc:dd:ee:02"); + when(updated.getIp()).thenReturn("10.0.0.1"); + when(updated.getIp6Address()).thenReturn(null); + + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(new NicTO[]{original}); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(updatedTO.getNics()).thenReturn(new NicTO[]{updated}); + when(nicsDao.findByUuid("uuid-1")).thenReturn(null); + + manager.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(nicsDao, never()).update(anyLong(), any()); + } + + // ---- updateExternalVmFromPrepareAnswer ---- + + @Test + public void updateExternalVmFromPrepareAnswerNoOpsWhenUpdatedNull() { + VirtualMachineTO vmTO = mockVmTO(VM_ID); + + manager.updateExternalVmFromPrepareAnswer(vmTO, null); + + verify(userVmDao, never()).findById(anyLong()); + verify(nicsDao, never()).update(anyLong(), any()); + } + + // ---- processPrepareExternalProvisioning ---- + + @Test + public void processPrepareExternalProvisioningThrowsOnAgentUnavailable() throws Exception { + Host host = mockHost(HypervisorType.External); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + DataCenter dataCenter = mock(DataCenter.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(new NicTO[0]); + when(vmTO.getExternalDetails()).thenReturn(Collections.emptyMap()); + when(nicsDao.listByVmId(anyLong())).thenReturn(Collections.emptyList()); + when(extensionsManager.getExternalAccessDetails(any(Host.class), anyMap())).thenReturn(Collections.emptyMap()); + when(agentMgr.send(eq(HOST_ID), any(com.cloud.agent.api.Command.class))).thenThrow(new AgentUnavailableException("boom", HOST_ID)); + + assertThrows(CloudRuntimeException.class, + () -> manager.processPrepareExternalProvisioning(true, host, vmProfile, dataCenter, vmTO)); + } + + @Test + public void processPrepareExternalProvisioningThrowsOnNullAnswer() throws Exception { + Host host = mockHost(HypervisorType.External); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + DataCenter dataCenter = mock(DataCenter.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(new NicTO[]{mock(NicTO.class)}); + when(vmTO.getExternalDetails()).thenReturn(Collections.emptyMap()); + when(extensionsManager.getExternalAccessDetails(any(Host.class), anyMap())).thenReturn(Collections.emptyMap()); + when(agentMgr.send(eq(HOST_ID), any(com.cloud.agent.api.Command.class))).thenReturn(null); + + assertThrows(CloudRuntimeException.class, + () -> manager.processPrepareExternalProvisioning(true, host, vmProfile, dataCenter, vmTO)); + } + + @Test + public void processPrepareExternalProvisioningThrowsOnNegativeResult() throws Exception { + Host host = mockHost(HypervisorType.External); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + DataCenter dataCenter = mock(DataCenter.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(new NicTO[]{mock(NicTO.class)}); + when(vmTO.getExternalDetails()).thenReturn(Collections.emptyMap()); + when(extensionsManager.getExternalAccessDetails(any(Host.class), anyMap())).thenReturn(Collections.emptyMap()); + PrepareExternalProvisioningAnswer answer = mock(PrepareExternalProvisioningAnswer.class); + when(answer.getResult()).thenReturn(false); + when(agentMgr.send(eq(HOST_ID), any(com.cloud.agent.api.Command.class))).thenReturn((Answer) answer); + + assertThrows(CloudRuntimeException.class, + () -> manager.processPrepareExternalProvisioning(true, host, vmProfile, dataCenter, vmTO)); + } + + @Test + public void processPrepareExternalProvisioningPopulatesNicsWhenEmpty() throws Exception { + Host host = mockHost(HypervisorType.External); + VirtualMachine vm = mock(VirtualMachine.class); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getId()).thenReturn(VM_ID); + when(vmProfile.getVirtualMachine()).thenReturn(vm); + DataCenter dataCenter = mock(DataCenter.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getNics()).thenReturn(new NicTO[0]); + when(vmTO.getExternalDetails()).thenReturn(Collections.emptyMap()); + + NicVO nicVO = mock(NicVO.class); + when(nicsDao.listByVmId(VM_ID)).thenReturn(Collections.singletonList(nicVO)); + NicProfile nicProfile = mock(NicProfile.class); + when(networkModel.getNicProfile(eq(vm), eq(nicVO), eq(dataCenter))).thenReturn(nicProfile); + HypervisorGuru guru = mock(HypervisorGuru.class); + when(hvGuruMgr.getGuru(HypervisorType.External)).thenReturn(guru); + NicTO nicTO = mock(NicTO.class); + when(nicTO.getDeviceId()).thenReturn(0); + when(guru.toNicTO(nicProfile)).thenReturn(nicTO); + + when(extensionsManager.getExternalAccessDetails(any(Host.class), anyMap())).thenReturn(Collections.emptyMap()); + PrepareExternalProvisioningAnswer answer = mock(PrepareExternalProvisioningAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVirtualMachineTO()).thenReturn(null); + when(agentMgr.send(eq(HOST_ID), any(com.cloud.agent.api.Command.class))).thenReturn((Answer) answer); + + manager.processPrepareExternalProvisioning(true, host, vmProfile, dataCenter, vmTO); + + verify(vmTO).setNics(any(NicTO[].class)); + verify(agentMgr).send(eq(HOST_ID), any(com.cloud.agent.api.Command.class)); + } + + // ---- updateStartCommandWithExternalDetails ---- + + @Test + public void updateStartCommandNoOpsForNonExternalHypervisor() { + Host host = mockHost(HypervisorType.KVM); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + StartCommand command = mock(StartCommand.class); + + manager.updateStartCommandWithExternalDetails(host, vmTO, command); + + verify(command, never()).setExternalDetails(anyMap()); + } + + @Test + public void updateStartCommandSetsExternalDetailsForExternalHypervisor() { + Host host = mockHost(HypervisorType.External); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + NicTO defaultNic = mock(NicTO.class); + when(defaultNic.isDefaultNic()).thenReturn(true); + when(vmTO.getNics()).thenReturn(new NicTO[]{defaultNic}); + Map details = new HashMap<>(); + when(vmTO.getExternalDetails()).thenReturn(details); + when(networkService.getNicVlanValueForExternalVm(defaultNic)).thenReturn("vlan-42"); + Map> resolved = new HashMap<>(); + when(extensionsManager.getExternalAccessDetails(eq(host), eq(details))).thenReturn(resolved); + StartCommand command = mock(StartCommand.class); + + manager.updateStartCommandWithExternalDetails(host, vmTO, command); + + assertEquals("vlan-42", details.get(VmDetailConstants.CLOUDSTACK_VLAN)); + verify(command).setExternalDetails(resolved); + } + + // ---- updateStopCommandForExternalHypervisorType ---- + + @Test + public void updateStopCommandNoOpsForNonExternalHypervisor() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + StopCommand stop = mock(StopCommand.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + + manager.updateStopCommandForExternalHypervisorType(HypervisorType.KVM, vmProfile, stop, vmTO); + + verify(stop, never()).setExternalDetails(anyMap()); + verify(hostDao, never()).findById(anyLong()); + } + + @Test + public void updateStopCommandNoOpsWhenHostIdNull() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getHostId()).thenReturn(null); + StopCommand stop = mock(StopCommand.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + + manager.updateStopCommandForExternalHypervisorType(HypervisorType.External, vmProfile, stop, vmTO); + + verify(hostDao, never()).findById(anyLong()); + } + + @Test + public void updateStopCommandNoOpsWhenHostNotFound() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getHostId()).thenReturn(HOST_ID); + when(hostDao.findById(HOST_ID)).thenReturn(null); + StopCommand stop = mock(StopCommand.class); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + + manager.updateStopCommandForExternalHypervisorType(HypervisorType.External, vmProfile, stop, vmTO); + + verify(stop, never()).setExternalDetails(anyMap()); + } + + @Test + public void updateStopCommandPopulatesDetailsAndClearsEmptyMaps() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getHostId()).thenReturn(HOST_ID); + HostVO host = mock(HostVO.class); + when(hostDao.findById(HOST_ID)).thenReturn(host); + + VirtualMachineTO vmTO = mockVmTO(VM_ID); + when(vmTO.getGuestOsDetails()).thenReturn(Collections.emptyMap()); + when(vmTO.getExtraConfig()).thenReturn(Collections.emptyMap()); + when(vmTO.getNetworkIdToNetworkNameMap()).thenReturn(Collections.emptyMap()); + when(vmTO.getExternalDetails()).thenReturn(Collections.emptyMap()); + + Map> resolved = new HashMap<>(); + when(extensionsManager.getExternalAccessDetails(eq(host), anyMap())).thenReturn(resolved); + StopCommand stop = mock(StopCommand.class); + + manager.updateStopCommandForExternalHypervisorType(HypervisorType.External, vmProfile, stop, vmTO); + + verify(vmTO).setGuestOsDetails(null); + verify(vmTO).setExtraConfig(null); + verify(vmTO).setNetworkIdToNetworkNameMap(null); + verify(stop).setVirtualMachine(vmTO); + verify(stop).setExternalDetails(resolved); + } + + // ---- updateRebootCommandWithExternalDetails ---- + + @Test + public void updateRebootCommandNoOpsForNonExternalHypervisor() { + Host host = mockHost(HypervisorType.VMware); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + RebootCommand cmd = mock(RebootCommand.class); + + manager.updateRebootCommandWithExternalDetails(host, vmTO, cmd); + + verify(cmd, never()).setExternalDetails(anyMap()); + } + + @Test + public void updateRebootCommandSetsExternalDetailsForExternalHypervisor() { + Host host = mockHost(HypervisorType.External); + VirtualMachineTO vmTO = mockVmTO(VM_ID); + Map details = Collections.emptyMap(); + when(vmTO.getExternalDetails()).thenReturn(details); + Map> resolved = new HashMap<>(); + when(extensionsManager.getExternalAccessDetails(eq(host), eq(details))).thenReturn(resolved); + RebootCommand cmd = mock(RebootCommand.class); + + manager.updateRebootCommandWithExternalDetails(host, vmTO, cmd); + + verify(cmd).setExternalDetails(resolved); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmIscsiTargetManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmIscsiTargetManagerImplTest.java new file mode 100644 index 000000000000..9f1dadeb20a0 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmIscsiTargetManagerImplTest.java @@ -0,0 +1,316 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ModifyTargetsCommand; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmIscsiTargetManagerImplTest { + + @Mock private HostDao hostDao; + @Mock private VolumeDao volumeDao; + @Mock private PrimaryDataStoreDao storagePoolDao; + @Mock private AgentManager agentMgr; + + @InjectMocks + private VmIscsiTargetManagerImpl manager; + + private static final long HOST_ID = 17L; + private static final long VM_ID = 42L; + private static final long POOL_ID_A = 101L; + private static final long POOL_ID_B = 202L; + + private HostVO mockHost(HypervisorType type) { + HostVO host = mock(HostVO.class); + when(host.getHypervisorType()).thenReturn(type); + return host; + } + + private VolumeVO mockVolume(long poolId, String iScsiName) { + VolumeVO volume = mock(VolumeVO.class); + when(volume.getPoolId()).thenReturn(poolId); + when(volume.get_iScsiName()).thenReturn(iScsiName); + return volume; + } + + private StoragePoolVO mockPool(boolean managed, String hostAddress, int port) { + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.isManaged()).thenReturn(managed); + if (managed) { + when(pool.getHostAddress()).thenReturn(hostAddress); + when(pool.getPort()).thenReturn(port); + } + return pool; + } + + // ---- getTargets ---- + + @Test + public void getTargetsReturnsEmptyWhenHostNotFound() { + when(hostDao.findById(HOST_ID)).thenReturn(null); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + verify(volumeDao, never()).findByInstance(anyLong()); + verify(storagePoolDao, never()).findById(anyLong()); + } + + @Test + public void getTargetsReturnsEmptyForNullHostId() { + when(hostDao.findById((Long) null)).thenReturn(null); + + List> targets = manager.getTargets(null, VM_ID); + + assertTrue(targets.isEmpty()); + verify(volumeDao, never()).findByInstance(anyLong()); + } + + @Test + public void getTargetsReturnsEmptyForNonVmwareHost() { + HostVO host = mockHost(HypervisorType.KVM); + when(hostDao.findById(HOST_ID)).thenReturn(host); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + verify(volumeDao, never()).findByInstance(anyLong()); + verify(storagePoolDao, never()).findById(anyLong()); + } + + @Test + public void getTargetsReturnsEmptyForXenServerHost() { + HostVO host = mockHost(HypervisorType.XenServer); + when(hostDao.findById(HOST_ID)).thenReturn(host); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + verify(volumeDao, never()).findByInstance(anyLong()); + } + + @Test + public void getTargetsReturnsEmptyForHyperVHost() { + HostVO host = mockHost(HypervisorType.Hyperv); + when(hostDao.findById(HOST_ID)).thenReturn(host); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + verify(volumeDao, never()).findByInstance(anyLong()); + } + + @Test + public void getTargetsReturnsEmptyWhenVmHasNoVolumes() { + HostVO host = mockHost(HypervisorType.VMware); + when(hostDao.findById(HOST_ID)).thenReturn(host); + when(volumeDao.findByInstance(VM_ID)).thenReturn(Collections.emptyList()); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + verify(storagePoolDao, never()).findById(anyLong()); + } + + @Test + public void getTargetsSkipsUnmanagedPools() { + HostVO host = mockHost(HypervisorType.VMware); + when(hostDao.findById(HOST_ID)).thenReturn(host); + VolumeVO volume = mockVolume(POOL_ID_A, "iqn.unmanaged"); + when(volumeDao.findByInstance(VM_ID)).thenReturn(Collections.singletonList(volume)); + StoragePoolVO pool = mockPool(false, null, 0); + when(storagePoolDao.findById(POOL_ID_A)).thenReturn(pool); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + } + + @Test + public void getTargetsSkipsNullPoolLookups() { + HostVO host = mockHost(HypervisorType.VMware); + when(hostDao.findById(HOST_ID)).thenReturn(host); + VolumeVO volume = mockVolume(POOL_ID_A, "iqn.orphan"); + when(volumeDao.findByInstance(VM_ID)).thenReturn(Collections.singletonList(volume)); + when(storagePoolDao.findById(POOL_ID_A)).thenReturn(null); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertTrue(targets.isEmpty()); + } + + @Test + public void getTargetsBuildsTupleFromSingleManagedVolume() { + HostVO host = mockHost(HypervisorType.VMware); + when(hostDao.findById(HOST_ID)).thenReturn(host); + VolumeVO volume = mockVolume(POOL_ID_A, "iqn.2001-04.com.example:storage.disk1"); + when(volumeDao.findByInstance(VM_ID)).thenReturn(Collections.singletonList(volume)); + StoragePoolVO pool = mockPool(true, "10.1.1.10", 3260); + when(storagePoolDao.findById(POOL_ID_A)).thenReturn(pool); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertEquals(1, targets.size()); + Map entry = targets.get(0); + assertEquals("10.1.1.10", entry.get(ModifyTargetsCommand.STORAGE_HOST)); + assertEquals("3260", entry.get(ModifyTargetsCommand.STORAGE_PORT)); + assertEquals("iqn.2001-04.com.example:storage.disk1", entry.get(ModifyTargetsCommand.IQN)); + } + + @Test + public void getTargetsHandlesMixedManagedAndUnmanagedVolumes() { + HostVO host = mockHost(HypervisorType.VMware); + when(hostDao.findById(HOST_ID)).thenReturn(host); + VolumeVO managed = mockVolume(POOL_ID_A, "iqn.managed"); + VolumeVO unmanaged = mockVolume(POOL_ID_B, "iqn.unmanaged"); + when(volumeDao.findByInstance(VM_ID)).thenReturn(Arrays.asList(managed, unmanaged)); + StoragePoolVO managedPool = mockPool(true, "10.1.1.10", 3260); + StoragePoolVO unmanagedPool = mockPool(false, null, 0); + when(storagePoolDao.findById(POOL_ID_A)).thenReturn(managedPool); + when(storagePoolDao.findById(POOL_ID_B)).thenReturn(unmanagedPool); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertEquals(1, targets.size()); + assertEquals("iqn.managed", targets.get(0).get(ModifyTargetsCommand.IQN)); + } + + @Test + public void getTargetsCollectsAllManagedTuplesWhenMultipleVolumesAreManaged() { + HostVO host = mockHost(HypervisorType.VMware); + when(hostDao.findById(HOST_ID)).thenReturn(host); + VolumeVO a = mockVolume(POOL_ID_A, "iqn.a"); + VolumeVO b = mockVolume(POOL_ID_B, "iqn.b"); + when(volumeDao.findByInstance(VM_ID)).thenReturn(Arrays.asList(a, b)); + StoragePoolVO poolA = mockPool(true, "10.1.1.10", 3260); + StoragePoolVO poolB = mockPool(true, "10.1.1.11", 3261); + when(storagePoolDao.findById(POOL_ID_A)).thenReturn(poolA); + when(storagePoolDao.findById(POOL_ID_B)).thenReturn(poolB); + + List> targets = manager.getTargets(HOST_ID, VM_ID); + + assertEquals(2, targets.size()); + assertEquals("10.1.1.10", targets.get(0).get(ModifyTargetsCommand.STORAGE_HOST)); + assertEquals("3260", targets.get(0).get(ModifyTargetsCommand.STORAGE_PORT)); + assertEquals("10.1.1.11", targets.get(1).get(ModifyTargetsCommand.STORAGE_HOST)); + assertEquals("3261", targets.get(1).get(ModifyTargetsCommand.STORAGE_PORT)); + } + + // ---- removeDynamicTargets ---- + + @Test + public void removeDynamicTargetsSendsModifyTargetsWithDynamicRemovalConfigured() { + List> input = Collections.singletonList(Collections.singletonMap("iqn", "iqn.test")); + Answer ok = mock(Answer.class); + when(ok.getResult()).thenReturn(true); + when(agentMgr.easySend(eq(HOST_ID), any())).thenReturn(ok); + + manager.removeDynamicTargets(HOST_ID, input); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ModifyTargetsCommand.class); + verify(agentMgr).easySend(eq(HOST_ID), captor.capture()); + ModifyTargetsCommand sent = captor.getValue(); + assertEquals(input, sent.getTargets()); + assertTrue(sent.getApplyToAllHostsInCluster()); + assertFalse(sent.getAdd()); + assertEquals(ModifyTargetsCommand.TargetTypeToRemove.DYNAMIC, sent.getTargetTypeToRemove()); + } + + @Test + public void removeDynamicTargetsAcceptsEmptyTargetList() { + Answer ok = mock(Answer.class); + when(ok.getResult()).thenReturn(true); + when(agentMgr.easySend(eq(HOST_ID), any())).thenReturn(ok); + + manager.removeDynamicTargets(HOST_ID, Collections.emptyList()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(ModifyTargetsCommand.class); + verify(agentMgr).easySend(eq(HOST_ID), captor.capture()); + assertTrue(captor.getValue().getTargets().isEmpty()); + } + + // ---- sendModifyTargetsCommand ---- + + @Test + public void sendModifyTargetsCommandSwallowsNullAnswerWithoutThrowing() { + ModifyTargetsCommand cmd = new ModifyTargetsCommand(); + cmd.setTargets(Collections.singletonList(Collections.singletonMap("iqn", "iqn.test"))); + when(agentMgr.easySend(eq(HOST_ID), any())).thenReturn(null); + + manager.sendModifyTargetsCommand(cmd, HOST_ID); + + verify(agentMgr).easySend(eq(HOST_ID), any()); + } + + @Test + public void sendModifyTargetsCommandSwallowsFailureAnswerWithoutThrowing() { + ModifyTargetsCommand cmd = new ModifyTargetsCommand(); + cmd.setTargets(Collections.singletonList(Collections.singletonMap("iqn", "iqn.test"))); + Answer failure = mock(Answer.class); + when(failure.getResult()).thenReturn(false); + when(agentMgr.easySend(eq(HOST_ID), any())).thenReturn(failure); + + manager.sendModifyTargetsCommand(cmd, HOST_ID); + + verify(agentMgr).easySend(eq(HOST_ID), any()); + } + + @Test + public void sendModifyTargetsCommandReturnsCleanlyOnSuccessfulAnswer() { + ModifyTargetsCommand cmd = new ModifyTargetsCommand(); + cmd.setTargets(Collections.singletonList(Collections.singletonMap("iqn", "iqn.test"))); + Answer success = mock(Answer.class); + when(success.getResult()).thenReturn(true); + when(agentMgr.easySend(eq(HOST_ID), any())).thenReturn(success); + + manager.sendModifyTargetsCommand(cmd, HOST_ID); + + verify(agentMgr).easySend(eq(HOST_ID), any()); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmMetadataSyncServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmMetadataSyncServiceImplTest.java new file mode 100644 index 000000000000..d0e4ab38e6de --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmMetadataSyncServiceImplTest.java @@ -0,0 +1,146 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.Pair; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmMetadataSyncServiceImplTest { + + private static final String VM_NAME = "i-2-3-VM"; + private static final long VM_ID = 42L; + + @InjectMocks + private VmMetadataSyncServiceImpl service; + + @Mock + private UserVmDao userVmDao; + @Mock + private VMInstanceDao vmDao; + + @Test + public void syncVMMetaData_nullOrEmptyInputDoesNotQueryDaos() { + service.syncVMMetaData(null); + service.syncVMMetaData(Collections.emptyMap()); + + verifyNoInteractions(userVmDao, vmDao); + } + + @Test + public void syncVMMetaData_matchingUserWithSamePlatformDoesNotSaveDetails() { + when(userVmDao.getVmsDetailByNames(eq(Set.of(VM_NAME)), eq(VmDetailConstants.PLATFORM))).thenReturn( + List.of(vmDetail(VM_NAME, VirtualMachine.Type.User, VM_ID, "ubuntu"))); + + service.syncVMMetaData(Map.of(VM_NAME, "ubuntu")); + + verify(userVmDao, never()).findById(VM_ID); + verify(userVmDao, never()).saveDetails(org.mockito.ArgumentMatchers.any(UserVmVO.class)); + verifyNoInteractions(vmDao); + } + + @Test + public void syncVMMetaData_matchingUserWithNewPlatformUpdatesDetailsAndRemovesTimeOffset() { + UserVmVO userVm = userVmWithDetails(Map.of( + VmDetailConstants.TIME_OFFSET, "old-offset", + VmDetailConstants.HYPERVISOR_TOOLS_VERSION, "old-driver")); + when(userVmDao.getVmsDetailByNames(eq(Set.of(VM_NAME)), eq(VmDetailConstants.PLATFORM))).thenReturn( + List.of(vmDetail(VM_NAME, VirtualMachine.Type.User, VM_ID, "old-platform"))); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + + service.syncVMMetaData(Map.of(VM_NAME, "new-platform")); + + assertEquals("new-platform", userVm.getDetails().get(VmDetailConstants.PLATFORM)); + assertEquals("xenserver56", userVm.getDetails().get(VmDetailConstants.HYPERVISOR_TOOLS_VERSION)); + assertFalse(userVm.getDetails().containsKey(VmDetailConstants.TIME_OFFSET)); + verify(userVmDao).loadDetails(userVm); + verify(userVmDao).saveDetails(userVm); + } + + @Test + public void syncVMMetaData_deviceIdPlatformSetsXenserver61Driver() { + UserVmVO userVm = userVmWithDetails(Collections.emptyMap()); + when(userVmDao.getVmsDetailByNames(eq(Set.of(VM_NAME)), eq(VmDetailConstants.PLATFORM))).thenReturn( + List.of(vmDetail(VM_NAME, VirtualMachine.Type.User, VM_ID, "old-platform"))); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + + service.syncVMMetaData(Map.of(VM_NAME, "xenserver device_id present")); + + assertEquals("xenserver61", userVm.getDetails().get(VmDetailConstants.HYPERVISOR_TOOLS_VERSION)); + verify(userVmDao).saveDetails(userVm); + } + + @Test + public void syncVMMetaData_missingJoinDetailUpdatesFallbackUserVm() { + VMInstanceVO vm = org.mockito.Mockito.mock(VMInstanceVO.class); + UserVmVO userVm = userVmWithDetails(Collections.emptyMap()); + when(userVmDao.getVmsDetailByNames(eq(Set.of(VM_NAME)), eq(VmDetailConstants.PLATFORM))).thenReturn(Collections.emptyList()); + when(vmDao.findVMByInstanceName(VM_NAME)).thenReturn(vm); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(vm.getId()).thenReturn(VM_ID); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + + service.syncVMMetaData(Map.of(VM_NAME, "fallback-platform")); + + assertEquals("fallback-platform", userVm.getDetails().get(VmDetailConstants.PLATFORM)); + verify(userVmDao).saveDetails(userVm); + } + + @Test + public void syncVMMetaData_matchingNonUserVmDoesNotFallbackOrSave() { + when(userVmDao.getVmsDetailByNames(eq(Set.of(VM_NAME)), eq(VmDetailConstants.PLATFORM))).thenReturn( + List.of(vmDetail(VM_NAME, VirtualMachine.Type.DomainRouter, VM_ID, "old-platform"))); + + service.syncVMMetaData(Map.of(VM_NAME, "new-platform")); + + verifyNoInteractions(vmDao); + verify(userVmDao, never()).findById(VM_ID); + verify(userVmDao, never()).saveDetails(org.mockito.ArgumentMatchers.any(UserVmVO.class)); + } + + private UserVmVO userVmWithDetails(Map details) { + UserVmVO userVm = new UserVmVO(); + userVm.setDetails(new HashMap<>(details)); + return userVm; + } + + private Pair, Pair> vmDetail(String vmName, VirtualMachine.Type type, long vmId, String platform) { + return new Pair<>(new Pair<>(vmName, type), new Pair<>(vmId, platform)); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmMigrateAwayPlanningServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmMigrateAwayPlanningServiceImplTest.java new file mode 100644 index 000000000000..575a484baa8c --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmMigrateAwayPlanningServiceImplTest.java @@ -0,0 +1,293 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.ScopeType; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmMigrateAwayPlanningServiceImplTest { + + private static final String VM_UUID = "vm-uuid"; + private static final long VM_ID = 42L; + private static final long SRC_HOST_ID = 7L; + private static final long DATA_CENTER_ID = 8L; + private static final long POD_ID = 9L; + private static final long CLUSTER_ID = 10L; + private static final long POOL_ID = 11L; + + @Spy + @InjectMocks + private VmMigrateAwayPlanningServiceImpl service = new VmMigrateAwayPlanningServiceImpl(); + + @Mock + private VMInstanceDao vmDao; + @Mock + private ServiceOfferingDao offeringDao; + @Mock + private HostDao hostDao; + @Mock + private VolumeDao volsDao; + @Mock + private PrimaryDataStoreDao storagePoolDao; + @Mock + private ClusterDao clusterDao; + @Mock + private DeploymentPlanningManager dpMgr; + @Mock + private HighAvailabilityManager haMgr; + @Mock + private VmWorkJobQueueService vmWorkJobQueueService; + @Mock + private VirtualMachineManagerImpl virtualMachineManager; + @Mock + private VMInstanceVO vm; + @Mock + private HostVO host; + + private ConfigDepotImpl originalConfigDepot; + private boolean configDepotOverridden; + + @After + public void cleanup() { + if (configDepotOverridden) { + ReflectionTestUtils.setField(MIGRATE_VM_ACROSS_CLUSTERS, "s_depot", originalConfigDepot); + } + } + + @Test + public void migrateAwayDispatchesThroughJobQueueWhenNotAlreadyInWorkJob() throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + Outcome outcome = mock(Outcome.class); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(false); + when(vmWorkJobQueueService.migrateVmAwayThroughJobQueue(VM_UUID, SRC_HOST_ID)).thenReturn(outcome); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.migrateAway(VM_UUID, SRC_HOST_ID); + } + + verify(vmWorkJobQueueService).retrieveVmFromJobOutcome(outcome, VM_UUID, "migrateVmAway"); + verify(vmWorkJobQueueService).retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } + + @Test + public void migrateAwayCreatesPlaceholderAndRetriesWithHaPlannerWhenAlreadyInWorkJob() throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + VmWorkJobVO placeholder = new VmWorkJobVO(""); + DeploymentPlanner haPlanner = mock(DeploymentPlanner.class); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(true); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vmWorkJobQueueService.createPlaceHolderWork(VM_ID)).thenReturn(placeholder); + when(haMgr.getHAPlanner()).thenReturn(haPlanner); + doThrow(new InsufficientServerCapacityException("capacity", DataCenter.class, DATA_CENTER_ID)) + .doNothing() + .when(service).orchestrateMigrateAway(VM_UUID, SRC_HOST_ID, null); + doNothing().when(service).orchestrateMigrateAway(VM_UUID, SRC_HOST_ID, haPlanner); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.migrateAway(VM_UUID, SRC_HOST_ID); + } + + verify(service).orchestrateMigrateAway(VM_UUID, SRC_HOST_ID, null); + verify(service).orchestrateMigrateAway(VM_UUID, SRC_HOST_ID, haPlanner); + verify(vmWorkJobQueueService).expungePlaceHolderWork(placeholder); + } + + @Test + public void orchestrateMigrateAwayPlansDestinationAndDelegatesLiveMigrationToManager() throws Exception { + DeploymentPlanner planner = mock(DeploymentPlanner.class); + DeployDestination destination = mock(DeployDestination.class); + Host destinationHost = mock(Host.class); + ServiceOfferingVO offering = mock(ServiceOfferingVO.class); + VolumeVO rootVolume = mock(VolumeVO.class); + StoragePoolVO rootDiskPool = mock(StoragePoolVO.class); + prepareVmAndHost(); + when(offeringDao.findById(VM_ID, 12L)).thenReturn(offering); + when(volsDao.findReadyRootVolumesByInstance(VM_ID)).thenReturn(List.of(rootVolume)); + when(rootVolume.getPoolId()).thenReturn(POOL_ID); + when(storagePoolDao.findById(POOL_ID)).thenReturn(rootDiskPool); + when(rootDiskPool.getId()).thenReturn(POOL_ID); + when(destination.getHost()).thenReturn(destinationHost); + when(destinationHost.getId()).thenReturn(13L); + when(dpMgr.planDeployment(any(VirtualMachineProfile.class), any(DataCenterDeployment.class), any(ExcludeList.class), eq(planner))).thenReturn(destination); + + service.orchestrateMigrateAway(VM_UUID, SRC_HOST_ID, planner); + + ArgumentCaptor planCaptor = ArgumentCaptor.forClass(DataCenterDeployment.class); + verify(dpMgr).planDeployment(any(VirtualMachineProfile.class), planCaptor.capture(), any(ExcludeList.class), eq(planner)); + assertTrue(planCaptor.getValue().isMigrationPlan()); + verify(virtualMachineManager).migrate(vm, SRC_HOST_ID, destination); + } + + @Test + public void orchestrateMigrateAwayMissingVmThrowsOriginalMessage() throws Exception { + CloudRuntimeException exception = org.junit.Assert.assertThrows(CloudRuntimeException.class, + () -> service.orchestrateMigrateAway(VM_UUID, SRC_HOST_ID, null)); + + assertEquals("Unable to find VM with uuid [vm-uuid].", exception.getMessage()); + } + + @Test + public void checkIfVmHasClusterWideVolumesReturnsTrueWhenAnyVolumePoolIsClusterScoped() { + VolumeVO zoneVolume = mock(VolumeVO.class); + VolumeVO clusterVolume = mock(VolumeVO.class); + StoragePoolVO zonePool = mock(StoragePoolVO.class); + StoragePoolVO clusterPool = mock(StoragePoolVO.class); + when(volsDao.findCreatedByInstance(VM_ID)).thenReturn(List.of(zoneVolume, clusterVolume)); + when(zoneVolume.getPoolId()).thenReturn(21L); + when(clusterVolume.getPoolId()).thenReturn(22L); + when(storagePoolDao.findById(21L)).thenReturn(zonePool); + when(storagePoolDao.findById(22L)).thenReturn(clusterPool); + when(zonePool.getScope()).thenReturn(ScopeType.ZONE); + when(clusterPool.getScope()).thenReturn(ScopeType.CLUSTER); + + assertTrue(service.checkIfVmHasClusterWideVolumes(VM_ID)); + } + + @Test + public void getMigrationDeploymentKeepsCurrentPodAndClusterWhenCrossClusterMigrationIsDisabled() { + when(host.getDataCenterId()).thenReturn(DATA_CENTER_ID); + when(host.getPodId()).thenReturn(POD_ID); + when(host.getClusterId()).thenReturn(CLUSTER_ID); + + DataCenterDeployment plan = service.getMigrationDeployment(vm, host, POOL_ID, new ExcludeList()); + + assertEquals(DATA_CENTER_ID, plan.getDataCenterId()); + assertEquals(Long.valueOf(POD_ID), plan.getPodId()); + assertEquals(Long.valueOf(CLUSTER_ID), plan.getClusterId()); + assertEquals(Long.valueOf(POOL_ID), plan.getPoolId()); + } + + @Test + public void getMigrationDeploymentForVmwareCrossClusterMigrationExcludesDifferentHypervisorClustersAndDropsUserVmPodAndCluster() { + ExcludeList excludes = new ExcludeList(); + ClusterVO vmwareCluster = mock(ClusterVO.class); + overrideMigrateAcrossClustersConfig(true); + when(host.getDataCenterId()).thenReturn(DATA_CENTER_ID); + when(host.getHypervisorType()).thenReturn(HypervisorType.VMware); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(clusterDao.listAllClusterIds(DATA_CENTER_ID)).thenReturn(new ArrayList<>(List.of(31L, 32L))); + when(clusterDao.listByDcHyType(DATA_CENTER_ID, HypervisorType.VMware.toString())).thenReturn(List.of(vmwareCluster)); + when(vmwareCluster.getId()).thenReturn(31L); + + DataCenterDeployment plan = service.getMigrationDeployment(vm, host, POOL_ID, excludes); + + assertEquals(DATA_CENTER_ID, plan.getDataCenterId()); + assertNull(plan.getPodId()); + assertNull(plan.getClusterId()); + assertEquals(Long.valueOf(POOL_ID), plan.getPoolId()); + assertTrue(excludes.getClustersToAvoid().contains(32L)); + } + + @Test + public void getMigrationDeploymentForSystemVmCrossClusterMigrationKeepsCurrentPod() { + ExcludeList excludes = new ExcludeList(); + overrideMigrateAcrossClustersConfig(true); + when(host.getDataCenterId()).thenReturn(DATA_CENTER_ID); + when(host.getPodId()).thenReturn(POD_ID); + when(host.getHypervisorType()).thenReturn(HypervisorType.VMware); + when(vm.getType()).thenReturn(VirtualMachine.Type.ConsoleProxy); + when(clusterDao.listAllClusterIds(DATA_CENTER_ID)).thenReturn(List.of()); + when(clusterDao.listByDcHyType(DATA_CENTER_ID, HypervisorType.VMware.toString())).thenReturn(List.of()); + + DataCenterDeployment plan = service.getMigrationDeployment(vm, host, POOL_ID, excludes); + + assertEquals(Long.valueOf(POD_ID), plan.getPodId()); + assertNull(plan.getClusterId()); + } + + private void prepareVmAndHost() { + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getServiceOfferingId()).thenReturn(12L); + when(vm.getHostId()).thenReturn(SRC_HOST_ID); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(hostDao.findById(SRC_HOST_ID)).thenReturn(host); + when(host.getDataCenterId()).thenReturn(DATA_CENTER_ID); + when(host.getPodId()).thenReturn(POD_ID); + when(host.getClusterId()).thenReturn(CLUSTER_ID); + } + + private void overrideMigrateAcrossClustersConfig(final boolean enabled) { + originalConfigDepot = (ConfigDepotImpl)ReflectionTestUtils.getField(MIGRATE_VM_ACROSS_CLUSTERS, "s_depot"); + ConfigDepotImpl configDepot = Mockito.mock(ConfigDepotImpl.class); + Mockito.when(configDepot.getConfigStringValue(Mockito.eq(MIGRATE_VM_ACROSS_CLUSTERS.key()), + Mockito.eq(ConfigKey.Scope.Zone), Mockito.eq(DATA_CENTER_ID))).thenReturn(Boolean.toString(enabled)); + ReflectionTestUtils.setField(MIGRATE_VM_ACROSS_CLUSTERS, "s_depot", configDepot); + configDepotOverridden = true; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmMigrationCheckpointServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmMigrationCheckpointServiceImplTest.java new file mode 100644 index 000000000000..821a5237b036 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmMigrationCheckpointServiceImplTest.java @@ -0,0 +1,189 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.RecreateCheckpointsCommand; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.snapshot.SnapshotManager; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class VmMigrationCheckpointServiceImplTest { + + @Spy + @InjectMocks + private VmMigrationCheckpointServiceImpl service; + + @Mock + private AgentManager agentManager; + @Mock + private VolumeOrchestrationService volumeOrchestrationService; + @Mock + private SnapshotManager snapshotManager; + @Mock + private VolumeDao volumeDao; + @Mock + private VMInstanceVO vm; + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationNonKvmReturnsBeforeVolumeLookup() throws Exception { + when(vm.getHypervisorType()).thenReturn(HypervisorType.VMware); + + service.recreateCheckpointsKvmOnVmAfterMigration(vm, 7L); + + verify(volumeDao, never()).findByInstance(anyLong()); + verify(agentManager, never()).send(eq(7L), any(RecreateCheckpointsCommand.class)); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationKvmWithoutCheckpointVolumesReturnsBeforeAgentCall() throws Exception { + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + doReturn(List.of()).when(service).getVmVolumesWithCheckpointsToRecreate(vm); + + service.recreateCheckpointsKvmOnVmAfterMigration(vm, 7L); + + verify(agentManager, never()).send(eq(7L), any(RecreateCheckpointsCommand.class)); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationAgentUnavailableEndsSnapshotChainAndThrows() throws Exception { + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + doReturn(List.of(checkpointVolume(11L))).when(service).getVmVolumesWithCheckpointsToRecreate(vm); + doThrow(new AgentUnavailableException(7L)).when(agentManager).send(eq(7L), any(RecreateCheckpointsCommand.class)); + + assertThrows(CloudRuntimeException.class, () -> service.recreateCheckpointsKvmOnVmAfterMigration(vm, 7L)); + + verify(snapshotManager).endSnapshotChainForVolume(11L, HypervisorType.KVM); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationOperationTimeoutEndsSnapshotChainAndThrows() throws Exception { + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + doReturn(List.of(checkpointVolume(11L))).when(service).getVmVolumesWithCheckpointsToRecreate(vm); + doThrow(new OperationTimedoutException(null, 7L, 0L, 0, false)).when(agentManager).send(eq(7L), any(RecreateCheckpointsCommand.class)); + + assertThrows(CloudRuntimeException.class, () -> service.recreateCheckpointsKvmOnVmAfterMigration(vm, 7L)); + + verify(snapshotManager).endSnapshotChainForVolume(11L, HypervisorType.KVM); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationFailedAnswerEndsSnapshotChainWithoutThrowing() throws Exception { + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + doReturn(List.of(checkpointVolume(11L))).when(service).getVmVolumesWithCheckpointsToRecreate(vm); + when(agentManager.send(eq(7L), any(RecreateCheckpointsCommand.class))).thenReturn(new Answer(null, false, "failed")); + + service.recreateCheckpointsKvmOnVmAfterMigration(vm, 7L); + + verify(snapshotManager).endSnapshotChainForVolume(11L, HypervisorType.KVM); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationSuccessfulAnswerDoesNotEndSnapshotChain() throws Exception { + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + doReturn(List.of(checkpointVolume(11L))).when(service).getVmVolumesWithCheckpointsToRecreate(vm); + when(agentManager.send(eq(7L), any(RecreateCheckpointsCommand.class))).thenReturn(new Answer(null, true, null)); + + service.recreateCheckpointsKvmOnVmAfterMigration(vm, 7L); + + verify(snapshotManager, never()).endSnapshotChainForVolume(anyLong(), eq(HypervisorType.KVM)); + } + + @Test + public void getVmVolumesWithCheckpointsToRecreateFiltersVolumesWithoutCheckpointPaths() { + VolumeVO volumeWithoutCheckpoints = mock(VolumeVO.class); + VolumeVO volumeWithCheckpoints = mock(VolumeVO.class); + when(vm.getId()).thenReturn(42L); + when(volumeWithoutCheckpoints.getId()).thenReturn(1L); + when(volumeWithCheckpoints.getId()).thenReturn(2L); + when(volumeWithCheckpoints.getPath()).thenReturn("volume-path"); + when(volumeDao.findByInstance(42L)).thenReturn(List.of(volumeWithoutCheckpoints, volumeWithCheckpoints)); + when(volumeOrchestrationService.getVolumeCheckpointPathsAndImageStoreUrls(1L, HypervisorType.KVM)) + .thenReturn(new Pair<>(List.of(), Set.of())); + when(volumeOrchestrationService.getVolumeCheckpointPathsAndImageStoreUrls(2L, HypervisorType.KVM)) + .thenReturn(new Pair<>(List.of("checkpoint-path"), Set.of("store-url"))); + + List result = service.getVmVolumesWithCheckpointsToRecreate(vm); + + assertEquals(1, result.size()); + assertEquals(List.of("checkpoint-path"), result.get(0).getCheckpointPaths()); + assertEquals(Set.of("store-url"), result.get(0).getCheckpointImageStoreUrls()); + assertEquals("volume-path", result.get(0).getPath()); + } + + @Test + public void endSnapshotChainForVolumesUsesDestinationVolumeInTargetPool() { + Volume sourceVolume = mock(Volume.class); + StoragePool targetPool = mock(StoragePool.class); + VolumeVO destinationVolume = mock(VolumeVO.class); + Map volumeToPoolMap = new HashMap<>(); + volumeToPoolMap.put(sourceVolume, targetPool); + when(sourceVolume.getName()).thenReturn("data"); + when(targetPool.getId()).thenReturn(20L); + when(volumeDao.findByPoolIdName(20L, "data")).thenReturn(destinationVolume); + when(destinationVolume.getId()).thenReturn(33L); + + service.endSnapshotChainForVolumes(volumeToPoolMap, HypervisorType.KVM); + + verify(snapshotManager).endSnapshotChainForVolume(33L, HypervisorType.KVM); + } + + private VolumeObjectTO checkpointVolume(long id) { + VolumeObjectTO volume = new VolumeObjectTO(); + volume.setId(id); + volume.setCheckpointPaths(List.of("checkpoint-path")); + volume.setCheckpointImageStoreUrls(Set.of("store-url")); + volume.setPath("volume-path"); + return volume; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmNetworkAttachmentOrchestrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmNetworkAttachmentOrchestrationServiceImplTest.java new file mode 100644 index 000000000000..f82dabd987ad --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmNetworkAttachmentOrchestrationServiceImplTest.java @@ -0,0 +1,266 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.dc.DataCenter; +import com.cloud.deploy.DeployDestination; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmNetworkAttachmentOrchestrationServiceImplTest { + + private static final long VM_ID = 11L; + private static final long NIC_ID = 22L; + private static final long NETWORK_ID = 33L; + private static final long ZONE_ID = 44L; + private static final long HOST_ID = 55L; + + @InjectMocks + private VmNetworkAttachmentOrchestrationServiceImpl service; + + @Mock + private NetworkOrchestrationService networkMgr; + @Mock + private NetworkModel networkModel; + @Mock + private VMInstanceDao vmDao; + @Mock + private NicDao nicsDao; + @Mock + private HostDao hostDao; + @Mock + private NetworkDao networkDao; + @Mock + private HypervisorGuruManager hvGuruMgr; + @Mock + private EntityManager entityMgr; + @Mock + private UserVmManager userVmMgr; + @Mock + private VmNetworkAttachmentOrchestrationService.BackendNicOperations backendNicOperations; + + @Mock + private VirtualMachine vm; + @Mock + private VMInstanceVO vmVO; + @Mock + private Network network; + @Mock + private NetworkVO networkVO; + @Mock + private NicProfile requested; + @Mock + private NicProfile createdNic; + @Mock + private Nic nic; + @Mock + private NicVO nicVO; + @Mock + private NicVO lock; + @Mock + private DataCenter dataCenter; + @Mock + private HostVO host; + @Mock + private HypervisorGuru hypervisorGuru; + @Mock + private VirtualMachineTO vmTO; + @Mock + private NicTO nicTO; + + @Test + public void checkIfNetworkExistsForUserVMThrowsWhenUserVmAlreadyHasNicInNetwork() { + NicVO existingNic = mock(NicVO.class); + when(vm.getType()).thenReturn(Type.User); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getInstanceName()).thenReturn("i-11-VM"); + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getUuid()).thenReturn("network-uuid"); + when(existingNic.getNetworkId()).thenReturn(NETWORK_ID); + when(nicsDao.listByVmId(VM_ID)).thenReturn(java.util.List.of(existingNic)); + + assertThrows(CloudRuntimeException.class, () -> service.checkIfNetworkExistsForUserVM(vm, network)); + } + + @Test + public void addVmToNetworkCreatesStoppedNicWithoutBackendPlug() throws Exception { + mockBasicVmAndNetwork(State.Stopped); + when(networkMgr.createNicForVm(eq(network), eq(requested), any(ReservationContext.class), any(VirtualMachineProfile.class), eq(false))).thenReturn(createdNic); + + try (MockedStatic ignored = mockCallContext()) { + NicProfile result = service.addVmToNetwork(vm, network, requested, backendNicOperations); + + assertSame(createdNic, result); + verify(networkMgr).createNicForVm(eq(network), eq(requested), any(ReservationContext.class), any(VirtualMachineProfile.class), eq(false)); + verify(backendNicOperations, never()).plugNic(any(), any(), any(), any(), any()); + } + } + + @Test + public void addVmToNetworkRemovesCreatedNicWhenBackendPlugFails() throws Exception { + mockBasicVmAndNetwork(State.Running); + when(vmVO.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(createdNic.getId()).thenReturn(NIC_ID); + when(networkMgr.createNicForVm(eq(network), eq(requested), any(ReservationContext.class), any(VirtualMachineProfile.class), eq(true))).thenReturn(createdNic); + when(hvGuruMgr.getGuru(HypervisorType.KVM)).thenReturn(hypervisorGuru); + when(hypervisorGuru.implement(any(VirtualMachineProfile.class))).thenReturn(vmTO); + when(hypervisorGuru.toNicTO(createdNic)).thenReturn(nicTO); + when(backendNicOperations.plugNic(eq(network), eq(nicTO), eq(vmTO), any(ReservationContext.class), any(DeployDestination.class))).thenReturn(false); + when(nicsDao.findById(NIC_ID)).thenReturn(nicVO); + + try (MockedStatic ignored = mockCallContext()) { + NicProfile result = service.addVmToNetwork(vm, network, requested, backendNicOperations); + + assertNull(result); + verify(networkMgr).removeNic(any(VirtualMachineProfile.class), eq(nicVO)); + } + } + + @Test + public void toNicTODelegatesToHypervisorGuru() { + when(hvGuruMgr.getGuru(HypervisorType.KVM)).thenReturn(hypervisorGuru); + when(hypervisorGuru.toNicTO(createdNic)).thenReturn(nicTO); + + NicTO result = service.toNicTO(createdNic, HypervisorType.KVM); + + assertSame(nicTO, result); + } + + @Test + public void removeNicFromVmReleasesAndRemovesStoppedNicWithoutBackendUnplug() throws Exception { + mockBasicVmAndNetwork(State.Stopped); + mockRemoveNicNetwork(); + when(nic.getId()).thenReturn(NIC_ID); + + try (MockedStatic ignored = mockCallContext()) { + assertTrue(service.removeNicFromVm(vm, nic, backendNicOperations)); + + verify(backendNicOperations, never()).unplugNic(any(), any(), any(), any(), any()); + verify(networkMgr).releaseNic(any(VirtualMachineProfile.class), eq(nic)); + verify(networkMgr).removeNic(any(VirtualMachineProfile.class), eq(nic)); + verify(nicsDao).remove(NIC_ID); + } + } + + @Test + public void removeVmFromNetworkRejectsDefaultUserNic() throws Exception { + mockBasicVmAndNetwork(State.Stopped); + mockRemoveVmFromNetworkVmTo(); + when(network.getId()).thenReturn(NETWORK_ID); + when(networkModel.getNicInNetwork(VM_ID, NETWORK_ID)).thenReturn(nic); + when(nic.isDefaultNic()).thenReturn(true); + when(vm.getType()).thenReturn(Type.User); + + try (MockedStatic ignored = mockCallContext()) { + assertThrows(CloudRuntimeException.class, () -> service.removeVmFromNetwork(vm, network, null, backendNicOperations)); + + verify(nicsDao, never()).acquireInLockTable(any()); + } + } + + @Test + public void removeVmFromNetworkReturnsTrueWhenNicDisappearsBeforeLock() throws Exception { + mockBasicVmAndNetwork(State.Stopped); + mockRemoveVmFromNetworkVmTo(); + when(network.getId()).thenReturn(NETWORK_ID); + when(networkModel.getNicInNetwork(VM_ID, NETWORK_ID)).thenReturn(nic); + when(nic.getId()).thenReturn(NIC_ID); + when(nicsDao.acquireInLockTable(NIC_ID)).thenReturn(null); + when(nicsDao.findById(NIC_ID)).thenReturn(null); + + try (MockedStatic ignored = mockCallContext()) { + assertTrue(service.removeVmFromNetwork(vm, network, null, backendNicOperations)); + } + } + + private void mockBasicVmAndNetwork(State state) { + when(vm.getId()).thenReturn(VM_ID); + when(vm.getHostId()).thenReturn(HOST_ID); + when(vm.getState()).thenReturn(state); + when(vm.getType()).thenReturn(Type.User); + when(vmDao.findById(VM_ID)).thenReturn(vmVO); + when(nicsDao.listByVmId(VM_ID)).thenReturn(java.util.Collections.emptyList()); + when(network.getDataCenterId()).thenReturn(ZONE_ID); + when(entityMgr.findById(DataCenter.class, ZONE_ID)).thenReturn(dataCenter); + when(hostDao.findById(HOST_ID)).thenReturn(host); + } + + private void mockRemoveNicNetwork() { + when(nic.getNetworkId()).thenReturn(NETWORK_ID); + when(networkDao.findById(NETWORK_ID)).thenReturn(networkVO); + when(networkVO.getId()).thenReturn(NETWORK_ID); + when(networkVO.getDataCenterId()).thenReturn(ZONE_ID); + when(vmVO.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(hvGuruMgr.getGuru(HypervisorType.KVM)).thenReturn(hypervisorGuru); + when(hypervisorGuru.implement(any(VirtualMachineProfile.class))).thenReturn(vmTO); + } + + private void mockRemoveVmFromNetworkVmTo() { + when(vmVO.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(hvGuruMgr.getGuru(HypervisorType.KVM)).thenReturn(hypervisorGuru); + when(hypervisorGuru.implement(any(VirtualMachineProfile.class))).thenReturn(vmTO); + } + + private MockedStatic mockCallContext() { + MockedStatic callContext = Mockito.mockStatic(CallContext.class); + CallContext currentContext = mock(CallContext.class); + when(currentContext.getCallingUser()).thenReturn(mock(User.class)); + when(currentContext.getCallingAccount()).thenReturn(mock(Account.class)); + callContext.when(CallContext::current).thenReturn(currentContext); + return callContext; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmNetworkNameMappingServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmNetworkNameMappingServiceImplTest.java new file mode 100644 index 000000000000..45da479dc173 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmNetworkNameMappingServiceImplTest.java @@ -0,0 +1,267 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Networks; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.network.vpc.dao.VpcDao; +import com.cloud.user.AccountVO; +import com.cloud.user.dao.AccountDao; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class VmNetworkNameMappingServiceImplTest { + + @InjectMocks + private VmNetworkNameMappingServiceImpl service; + + @Mock + private UserVmJoinDao userVmJoinDao; + @Mock + private DomainRouterJoinDao domainRouterJoinDao; + @Mock + private NetworkDao networkDao; + @Mock + private AccountDao accountDao; + @Mock + private DomainDao domainDao; + @Mock + private DataCenterDao dataCenterDao; + @Mock + private VpcDao vpcDao; + + private VMInstanceVO vm; + + @Before + public void setUp() { + vm = new VMInstanceVO(1L, 1L, "VM1", "i-2-2-VM", + VirtualMachine.Type.User, 1L, HypervisorType.KVM, 1L, 1L, 1L, + 1L, false, false); + ReflectionTestUtils.setField(vm, "dataCenterId", 1L); + } + + @Test + public void userVmWithNoJoinRowsDoesNotSetNetworkNameMap() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of()); + + service.setVmNetworkDetails(vm, vmTO); + + verify(vmTO, never()).setNetworkIdToNetworkNameMap(org.mockito.ArgumentMatchers.anyMap()); + } + + @Test + public void userVmNetworkWithoutVpcMapsToDomainAccountZoneAndNetwork() { + VirtualMachineTO vmTO = new VirtualMachineTO() { + }; + UserVmJoinVO userVmJoin = userVmJoin(5L); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of(userVmJoin)); + stubNetworkNameInputs(network(5L, 2L, 3L, null), account(2L), domain(3L), zone(1L)); + + service.setVmNetworkDetails(vm, vmTO); + + assertEquals(1, vmTO.getNetworkIdToNetworkNameMap().size()); + assertEquals("D3-A2-Z1-S5", vmTO.getNetworkIdToNetworkNameMap().get(5L)); + } + + @Test + public void userVmNetworkWithVpcMapsToDomainAccountZoneVpcAndNetwork() { + VirtualMachineTO vmTO = new VirtualMachineTO() { + }; + UserVmJoinVO userVmJoin = userVmJoin(5L); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of(userVmJoin)); + stubNetworkNameInputs(network(5L, 2L, 3L, 4L), account(2L), domain(3L), zone(1L)); + VpcVO vpc = vpc(4L); + when(vpcDao.findById(4L)).thenReturn(vpc); + + service.setVmNetworkDetails(vm, vmTO); + + assertEquals(1, vmTO.getNetworkIdToNetworkNameMap().size()); + assertEquals("D3-A2-Z1-V4-S5", vmTO.getNetworkIdToNetworkNameMap().get(5L)); + } + + @Test + public void domainRouterMapsOnlyGuestNetworksWithoutVpcAndNxsBroadcastDomain() { + VMInstanceVO routerVm = new VMInstanceVO(1L, 1L, "Router", "r-1-VM", + VirtualMachine.Type.DomainRouter, 1L, HypervisorType.KVM, 1L, 1L, 1L, + 1L, false, false); + ReflectionTestUtils.setField(routerVm, "dataCenterId", 1L); + VirtualMachineTO vmTO = new VirtualMachineTO() { + }; + DomainRouterJoinVO mappedJoin = routerJoin(5L); + DomainRouterJoinVO vpcJoin = routerJoin(6L); + DomainRouterJoinVO vlanJoin = routerJoin(7L); + when(domainRouterJoinDao.getRouterByIdAndTrafficType(1L, Networks.TrafficType.Guest)).thenReturn(List.of( + mappedJoin, vpcJoin, vlanJoin)); + NetworkVO mappedNetwork = network(5L, 2L, 3L, null, Networks.BroadcastDomainType.NSX); + NetworkVO vpcNetwork = network(6L, 2L, 3L, 4L, Networks.BroadcastDomainType.NSX); + NetworkVO vlanNetwork = network(7L, 2L, 3L, null, Networks.BroadcastDomainType.Vlan); + when(networkDao.findById(5L)).thenReturn(mappedNetwork); + when(networkDao.findById(6L)).thenReturn(vpcNetwork); + when(networkDao.findById(7L)).thenReturn(vlanNetwork); + AccountVO account = account(2L); + DomainVO domain = domain(3L); + DataCenterVO zone = zone(1L); + when(accountDao.findById(2L)).thenReturn(account); + when(domainDao.findById(3L)).thenReturn(domain); + when(dataCenterDao.findById(1L)).thenReturn(zone); + + service.setVmNetworkDetails(routerVm, vmTO); + + assertEquals(1, vmTO.getNetworkIdToNetworkNameMap().size()); + assertEquals("D3-A2-Z1-S5", vmTO.getNetworkIdToNetworkNameMap().get(5L)); + } + + @Test + public void missingZoneThrowsExistingMessage() { + UserVmJoinVO userVmJoin = userVmJoin(5L); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of(userVmJoin)); + stubNetworkNameInputs(network(5L, 2L, 3L, null), account(2L), domain(3L), null); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.setVmNetworkDetails(vm, new VirtualMachineTO() { + })); + + assertEquals("Failed to find zone with ID: 1", exception.getMessage()); + } + + @Test + public void missingAccountThrowsExistingMessage() { + UserVmJoinVO userVmJoin = userVmJoin(5L); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of(userVmJoin)); + stubNetworkNameInputs(network(5L, 2L, 3L, null), null, domain(3L), zone(1L)); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.setVmNetworkDetails(vm, new VirtualMachineTO() { + })); + + assertEquals("Failed to find account with ID: 2", exception.getMessage()); + } + + @Test + public void missingDomainThrowsExistingMessage() { + UserVmJoinVO userVmJoin = userVmJoin(5L); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of(userVmJoin)); + stubNetworkNameInputs(network(5L, 2L, 3L, null), account(2L), null, zone(1L)); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.setVmNetworkDetails(vm, new VirtualMachineTO() { + })); + + assertEquals("Failed to find domain with ID: 3", exception.getMessage()); + } + + @Test + public void missingVpcThrowsExistingMessage() { + UserVmJoinVO userVmJoin = userVmJoin(5L); + when(userVmJoinDao.searchByIds(anyLong())).thenReturn(List.of(userVmJoin)); + stubNetworkNameInputs(network(5L, 2L, 3L, 4L), account(2L), domain(3L), zone(1L)); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.setVmNetworkDetails(vm, new VirtualMachineTO() { + })); + + assertEquals("Failed to find VPC with ID: 4", exception.getMessage()); + } + + private UserVmJoinVO userVmJoin(long networkId) { + UserVmJoinVO userVmJoin = mock(UserVmJoinVO.class); + when(userVmJoin.getNetworkId()).thenReturn(networkId); + return userVmJoin; + } + + private DomainRouterJoinVO routerJoin(long networkId) { + DomainRouterJoinVO routerJoin = mock(DomainRouterJoinVO.class); + when(routerJoin.getNetworkId()).thenReturn(networkId); + return routerJoin; + } + + private NetworkVO network(long id, long accountId, long domainId, Long vpcId) { + return network(id, accountId, domainId, vpcId, Networks.BroadcastDomainType.NSX); + } + + private NetworkVO network(long id, long accountId, long domainId, Long vpcId, Networks.BroadcastDomainType broadcastDomainType) { + NetworkVO network = mock(NetworkVO.class); + when(network.getId()).thenReturn(id); + when(network.getAccountId()).thenReturn(accountId); + when(network.getDomainId()).thenReturn(domainId); + when(network.getVpcId()).thenReturn(vpcId); + when(network.getBroadcastDomainType()).thenReturn(broadcastDomainType); + return network; + } + + private AccountVO account(long id) { + AccountVO account = mock(AccountVO.class); + when(account.getId()).thenReturn(id); + return account; + } + + private DomainVO domain(long id) { + DomainVO domain = mock(DomainVO.class); + when(domain.getId()).thenReturn(id); + return domain; + } + + private DataCenterVO zone(long id) { + DataCenterVO zone = mock(DataCenterVO.class); + when(zone.getId()).thenReturn(id); + return zone; + } + + private VpcVO vpc(long id) { + VpcVO vpc = mock(VpcVO.class); + when(vpc.getId()).thenReturn(id); + return vpc; + } + + private void stubNetworkNameInputs(NetworkVO network, AccountVO account, DomainVO domain, DataCenterVO zone) { + long networkId = network.getId(); + long accountId = network.getAccountId(); + long domainId = network.getDomainId(); + when(networkDao.findById(networkId)).thenReturn(network); + when(accountDao.findById(accountId)).thenReturn(account); + when(domainDao.findById(domainId)).thenReturn(domain); + when(dataCenterDao.findById(1L)).thenReturn(zone); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmNicBackendCommandServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmNicBackendCommandServiceImplTest.java new file mode 100644 index 000000000000..f654eecc5aba --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmNicBackendCommandServiceImplTest.java @@ -0,0 +1,256 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.PlugNicAnswer; +import com.cloud.agent.api.PlugNicCommand; +import com.cloud.agent.api.ReplugNicAnswer; +import com.cloud.agent.api.ReplugNicCommand; +import com.cloud.agent.api.UnPlugNicAnswer; +import com.cloud.agent.api.UnPlugNicCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DataCenter; +import com.cloud.deploy.DeployDestination; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; +import com.cloud.network.Network; +import com.cloud.network.dao.NetworkDetailVO; +import com.cloud.network.dao.NetworkDetailsDao; +import com.cloud.offering.NetworkOffering; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmNicBackendCommandServiceImplTest { + + private static final long VM_ID = 42L; + private static final long NETWORK_ID = 101L; + private static final long HOST_ID = 202L; + private static final long DATA_CENTER_ID = 303L; + private static final String VM_NAME = "i-2-VM"; + + @InjectMocks + private VmNicBackendCommandServiceImpl service; + + @Mock + private AgentManager agentMgr; + @Mock + private VMInstanceDao vmDao; + @Mock + private UserVmDao userVmDao; + @Mock + private UserVmService userVmService; + @Mock + private NetworkDetailsDao networkDetailsDao; + @Mock + private VmVlanPersistenceMappingService vmVlanPersistenceMappingService; + @Mock + private Network network; + @Mock + private NicTO nic; + @Mock + private VirtualMachineTO vmTO; + @Mock + private Host host; + @Mock + private DeployDestination dest; + + @Test + public void plugNicAddsPvlanTypeToNicDetailsAndSendsCommand() throws Exception { + VMInstanceVO vm = runningVm(); + when(network.getId()).thenReturn(NETWORK_ID); + when(vmDao.findById(VM_ID)).thenReturn(vm); + when(dest.getHost()).thenReturn(host); + when(host.getId()).thenReturn(HOST_ID); + mockVmTo(); + NetworkDetailVO pvlanType = new NetworkDetailVO(NETWORK_ID, ApiConstants.ISOLATED_PVLAN_TYPE, "promiscuous", false); + when(networkDetailsDao.findDetail(NETWORK_ID, ApiConstants.ISOLATED_PVLAN_TYPE)).thenReturn(pvlanType); + Map nicDetails = new HashMap<>(); + when(nic.getDetails()).thenReturn(nicDetails); + answerWithPlugNicAnswer(true); + + boolean result = service.plugNic(network, nic, vmTO, null, dest); + + assertTrue(result); + assertEquals("promiscuous", nicDetails.get(NetworkOffering.Detail.pvlanType)); + PlugNicCommand command = captureSentCommands().getCommand(PlugNicCommand.class); + assertSame(nic, command.getNic()); + assertEquals(VM_NAME, command.getVmName()); + assertEquals(VirtualMachine.Type.User, command.getVMType()); + } + + @Test + public void unplugNicCollectsUserVmStatisticsAndPassesVlanPersistenceMap() throws Exception { + VMInstanceVO vm = runningVm(); + UserVmVO userVm = mock(UserVmVO.class); + Map vlanToPersistenceMap = Map.of("vlan://100", true); + when(vmDao.findById(VM_ID)).thenReturn(vm); + when(userVmDao.findById(VM_ID)).thenReturn(userVm); + when(userVm.getType()).thenReturn(VirtualMachine.Type.User); + when(dest.getHost()).thenReturn(host); + when(host.getId()).thenReturn(HOST_ID); + mockVmTo(); + when(vmVlanPersistenceMappingService.getVlanToPersistenceMapForVM(VM_ID)).thenReturn(vlanToPersistenceMap); + answerWithUnplugNicAnswer(true); + + boolean result = service.unplugNic(network, nic, vmTO, null, dest); + + assertTrue(result); + verify(userVmService).collectVmNetworkStatistics(userVm); + UnPlugNicCommand command = captureSentCommands().getCommand(UnPlugNicCommand.class); + assertSame(vlanToPersistenceMap, command.getVlanToPersistenceMap()); + } + + @Test + public void unplugNicSkipsBackendCommandForStoppedVm() throws Exception { + VMInstanceVO vm = vmWithState(State.Stopped); + when(vmDao.findById(VM_ID)).thenReturn(vm); + mockVmTo(); + + boolean result = service.unplugNic(network, nic, vmTO, null, dest); + + assertTrue(result); + verify(agentMgr, never()).send(eq(HOST_ID), any(Commands.class)); + verify(userVmService, never()).collectVmNetworkStatistics(any()); + } + + @Test + public void replugNicReturnsFalseWhenAgentAnswerFails() throws Exception { + VMInstanceVO vm = runningVm(); + when(vmDao.findById(VM_ID)).thenReturn(vm); + when(host.getId()).thenReturn(HOST_ID); + mockVmTo(); + answerWithReplugNicAnswer(false); + + boolean result = service.replugNic(network, nic, vmTO, host); + + assertFalse(result); + } + + @Test + public void plugNicWrapsTimeoutAsAgentUnavailableException() throws Exception { + VMInstanceVO vm = runningVm(); + when(vmDao.findById(VM_ID)).thenReturn(vm); + when(dest.getHost()).thenReturn(host); + when(host.getId()).thenReturn(HOST_ID); + mockVmTo(); + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenThrow(new OperationTimedoutException(null, HOST_ID, 0L, 0, false)); + + AgentUnavailableException exception = assertThrows(AgentUnavailableException.class, + () -> service.plugNic(network, nic, vmTO, null, dest)); + + assertTrue(exception.getMessage().contains("Unable to plug nic for router " + VM_NAME + " in network " + network)); + } + + @Test + public void plugNicThrowsResourceUnavailableWhenVmIsNotRunning() { + VMInstanceVO vm = vmWithState(State.Stopped); + when(vmDao.findById(VM_ID)).thenReturn(vm); + mockVmTo(); + + ResourceUnavailableException exception = assertThrows(ResourceUnavailableException.class, + () -> service.plugNic(network, nic, vmTO, null, dest)); + + assertTrue(exception.getMessage().contains("Unable to apply PlugNic")); + assertEquals(DataCenter.class, exception.getScope()); + } + + private VMInstanceVO runningVm() { + return vmWithState(State.Running); + } + + private VMInstanceVO vmWithState(State state) { + VMInstanceVO vm = new VMInstanceVO(); + vm.setState(state); + vm.setDataCenterId(DATA_CENTER_ID); + return vm; + } + + private void mockVmTo() { + when(vmTO.getId()).thenReturn(VM_ID); + when(vmTO.getName()).thenReturn(VM_NAME); + when(vmTO.getType()).thenReturn(VirtualMachine.Type.User); + when(vmTO.getDetails()).thenReturn(Map.of("platform", "test")); + } + + private void answerWithPlugNicAnswer(boolean result) throws Exception { + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenAnswer(invocation -> { + Commands commands = invocation.getArgument(1); + PlugNicCommand command = commands.getCommand(PlugNicCommand.class); + Answer[] answers = new Answer[] {new PlugNicAnswer(command, result, "result")}; + commands.setAnswers(answers); + return answers; + }); + } + + private void answerWithUnplugNicAnswer(boolean result) throws Exception { + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenAnswer(invocation -> { + Commands commands = invocation.getArgument(1); + UnPlugNicCommand command = commands.getCommand(UnPlugNicCommand.class); + Answer[] answers = new Answer[] {new UnPlugNicAnswer(command, result, "result")}; + commands.setAnswers(answers); + return answers; + }); + } + + private void answerWithReplugNicAnswer(boolean result) throws Exception { + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenAnswer(invocation -> { + Commands commands = invocation.getArgument(1); + ReplugNicCommand command = commands.getCommand(ReplugNicCommand.class); + Answer[] answers = new Answer[] {new ReplugNicAnswer(command, result, "result")}; + commands.setAnswers(answers); + return answers; + }); + } + + private Commands captureSentCommands() throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(Commands.class); + verify(agentMgr).send(eq(HOST_ID), captor.capture()); + return captor.getValue(); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmNicUpdateServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmNicUpdateServiceImplTest.java new file mode 100644 index 000000000000..ea10f7163b2a --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmNicUpdateServiceImplTest.java @@ -0,0 +1,183 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.UpdateVmNicAnswer; +import com.cloud.agent.api.UpdateVmNicCommand; +import com.cloud.agent.manager.Commands; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.vm.dao.NicDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmNicUpdateServiceImplTest { + + private static final long HOST_ID = 42L; + private static final long NIC_ID = 101L; + private static final long DEFAULT_NIC_ID = 102L; + private static final String VM_NAME = "i-2-VM"; + private static final String VM_UUID = "vm-uuid"; + private static final String NIC_UUID = "nic-uuid"; + private static final String MAC_ADDRESS = "02:00:00:00:00:01"; + + @InjectMocks + private VmNicUpdateServiceImpl service; + + @Mock + private AgentManager agentMgr; + @Mock + private NicDao nicsDao; + @Mock + private VirtualMachine vm; + @Mock + private Nic nic; + @Mock + private Nic defaultNic; + + @Test + public void updateDefaultNicForVMSwapsDefaultFlagAndDeviceIds() { + NicVO nicVO = new NicVO("reserver", 1L, 1L, VirtualMachine.Type.User); + nicVO.setDefaultNic(false); + nicVO.setDeviceId(4); + NicVO defaultNicVO = new NicVO("reserver", 1L, 1L, VirtualMachine.Type.User); + defaultNicVO.setDefaultNic(true); + defaultNicVO.setDeviceId(0); + when(nic.getId()).thenReturn(NIC_ID); + when(nic.getDeviceId()).thenReturn(4); + when(nic.getUuid()).thenReturn(NIC_UUID); + when(defaultNic.getId()).thenReturn(DEFAULT_NIC_ID); + when(defaultNic.getDeviceId()).thenReturn(0); + when(defaultNic.getUuid()).thenReturn("default-nic-uuid"); + when(nicsDao.findById(NIC_ID)).thenReturn(nicVO); + when(nicsDao.findById(DEFAULT_NIC_ID)).thenReturn(defaultNicVO); + + Boolean result = service.updateDefaultNicForVM(vm, nic, defaultNic); + + assertTrue(result); + assertTrue(nicVO.isDefaultNic()); + assertEquals(0, nicVO.getDeviceId()); + assertFalse(defaultNicVO.isDefaultNic()); + assertEquals(4, defaultNicVO.getDeviceId()); + verify(nicsDao).persist(nicVO); + verify(nicsDao).persist(defaultNicVO); + } + + @Test + public void updateVmNicPersistsEnabledForStoppedVmWithoutAgentCommand() throws Exception { + NicVO nicVO = new NicVO("reserver", 1L, 1L, VirtualMachine.Type.User); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(nic.getId()).thenReturn(NIC_ID); + when(nicsDao.findById(NIC_ID)).thenReturn(nicVO); + + boolean result = service.updateVmNic(vm, nic, false); + + assertTrue(result); + assertFalse(nicVO.isEnabled()); + verify(agentMgr, never()).send(eq(HOST_ID), any(Commands.class)); + verify(nicsDao).persist(nicVO); + } + + @Test + public void updateVmNicSendsUpdateCommandAndPersistsForRunningVm() throws Exception { + NicVO nicVO = new NicVO("reserver", 1L, 1L, VirtualMachine.Type.User); + mockRunningVmAndNic(); + when(nicsDao.findById(NIC_ID)).thenReturn(nicVO); + answerWithUpdateVmNicAnswer(true); + + boolean result = service.updateVmNic(vm, nic, false); + + assertTrue(result); + Commands sentCommands = captureSentCommands(); + assertEquals(1, sentCommands.size()); + UpdateVmNicCommand command = (UpdateVmNicCommand)sentCommands.toCommands()[0]; + assertEquals(MAC_ADDRESS, command.getNicMacAddress()); + assertEquals(VM_NAME, command.getVmName()); + assertFalse(command.isEnabled()); + assertFalse(nicVO.isEnabled()); + verify(nicsDao).persist(nicVO); + } + + @Test + public void updateVmNicReturnsFalseWhenAgentAnswerFails() throws Exception { + mockRunningVmAndNic(); + answerWithUpdateVmNicAnswer(false); + + boolean result = service.updateVmNic(vm, nic, true); + + assertFalse(result); + verify(nicsDao, never()).persist(any(NicVO.class)); + } + + @Test + public void updateVmNicWrapsTimeoutAsAgentUnavailableException() throws Exception { + mockRunningVmAndNic(); + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenThrow(new OperationTimedoutException(null, HOST_ID, 0L, 0, false)); + + AgentUnavailableException exception = assertThrows(AgentUnavailableException.class, + () -> service.updateVmNic(vm, nic, true)); + + assertTrue(exception.getMessage().contains("Unable to update NIC " + NIC_UUID + " for VM " + VM_UUID + ".")); + verify(nicsDao, never()).persist(any(NicVO.class)); + } + + private void mockRunningVmAndNic() { + when(vm.getState()).thenReturn(VirtualMachine.State.Running); + when(vm.getHostId()).thenReturn(HOST_ID); + when(vm.getName()).thenReturn(VM_NAME); + when(vm.getUuid()).thenReturn(VM_UUID); + when(nic.getId()).thenReturn(NIC_ID); + when(nic.getUuid()).thenReturn(NIC_UUID); + when(nic.getMacAddress()).thenReturn(MAC_ADDRESS); + } + + private void answerWithUpdateVmNicAnswer(boolean result) throws Exception { + when(agentMgr.send(eq(HOST_ID), any(Commands.class))).thenAnswer(invocation -> { + Commands commands = invocation.getArgument(1); + Command command = commands.toCommands()[0]; + Answer[] answers = new Answer[] {new UpdateVmNicAnswer((UpdateVmNicCommand)command, result, "result")}; + commands.setAnswers(answers); + return answers; + }); + } + + private Commands captureSentCommands() throws Exception { + ArgumentCaptor captor = ArgumentCaptor.forClass(Commands.class); + verify(agentMgr).send(eq(HOST_ID), captor.capture()); + return captor.getValue(); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmOfflineStorageMigrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmOfflineStorageMigrationServiceImplTest.java new file mode 100644 index 000000000000..8a99fa037576 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmOfflineStorageMigrationServiceImplTest.java @@ -0,0 +1,410 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.MigrateVmToPoolAnswer; +import com.cloud.agent.api.MigrateVmToPoolCommand; +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.Storage; +import com.cloud.storage.StorageManager; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.fsm.NoTransitionException; +import com.cloud.vm.VirtualMachine.Event; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmOfflineStorageMigrationServiceImplTest { + + @Spy + @InjectMocks + private VmOfflineStorageMigrationServiceImpl service; + + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private VolumeDao volumeDao; + @Mock + private PrimaryDataStoreDao storagePoolDao; + @Mock + private ClusterDao clusterDao; + @Mock + private HostDao hostDao; + @Mock + private DiskOfferingDao diskOfferingDao; + @Mock + private ClusterDetailsDao clusterDetailsDao; + @Mock + private AgentManager agentMgr; + @Mock + private HypervisorGuruManager hvGuruMgr; + @Mock + private NetworkOrchestrationService networkMgr; + @Mock + private VolumeOrchestrationService volumeMgr; + @Mock + private StorageManager storageMgr; + @Mock + private VmVolumeMigrationPlanningService vmVolumeMigrationPlanningService; + @Mock + private VirtualMachineManager virtualMachineManager; + + @Test + public void orchestrateStorageMigrationEmptyMappingThrowsAndAttemptsStoppedTransition() throws Exception { + VMInstanceVO vm = mockVm("vm-1"); + when(vmInstanceDao.findByUuid("vm-1")).thenReturn(vm); + + assertThrows(CloudRuntimeException.class, () -> service.orchestrateStorageMigration("vm-1", new HashMap<>())); + + verify(virtualMachineManager).stateTransitTo(vm, Event.AgentReportStopped, null); + } + + @Test + public void orchestrateStorageMigrationWrapsStorageMigrationFailureAndAttemptsStoppedTransition() throws Exception { + VMInstanceVO vm = mockVm("vm-2"); + Map volumeToPool = new HashMap<>(); + when(vmInstanceDao.findByUuid("vm-2")).thenReturn(vm); + doReturn(volumeToPool).when(service).prepareVmStorageMigration(eq(vm), any()); + doThrow(new StorageUnavailableException("offline failed", 1L)).when(service).migrateThroughHypervisorOrStorage(vm, volumeToPool); + + assertThrows(CloudRuntimeException.class, () -> service.orchestrateStorageMigration("vm-2", Map.of(1L, 2L))); + + verify(virtualMachineManager).stateTransitTo(vm, Event.AgentReportStopped, null); + } + + @Test + public void orchestrateStorageMigrationWrapsFailedStoppedTransition() throws Exception { + VMInstanceVO vm = mockVm("vm-3"); + Map volumeToPool = new HashMap<>(); + when(vmInstanceDao.findByUuid("vm-3")).thenReturn(vm); + doReturn(volumeToPool).when(service).prepareVmStorageMigration(eq(vm), any()); + doNothing().when(service).migrateThroughHypervisorOrStorage(vm, volumeToPool); + doThrow(new NoTransitionException("failed")).when(virtualMachineManager).stateTransitTo(vm, Event.AgentReportStopped, null); + + assertThrows(CloudRuntimeException.class, () -> service.orchestrateStorageMigration("vm-3", Map.of(1L, 2L))); + } + + @Test + public void prepareVmStorageMigrationRejectsEmptyVolumePoolMap() { + VMInstanceVO vm = mockVm("vm-4"); + + assertThrows(CloudRuntimeException.class, () -> service.prepareVmStorageMigration(vm, new HashMap<>())); + } + + @Test + public void prepareVmStorageMigrationBuildsClusterDeploymentAndRequestsStorageMigrationState() throws Exception { + VMInstanceVO vm = mockVm("vm-5"); + StoragePoolVO pool = mock(StoragePoolVO.class); + ClusterVO cluster = mock(ClusterVO.class); + Map requestedMap = Map.of(11L, 22L); + Map plannedMap = new HashMap<>(); + ArgumentCaptor planCaptor = ArgumentCaptor.forClass(DataCenterDeployment.class); + when(pool.getClusterId()).thenReturn(33L); + when(storagePoolDao.findById(22L)).thenReturn(pool); + when(clusterDao.findById(33L)).thenReturn(cluster); + when(cluster.getDataCenterId()).thenReturn(44L); + when(cluster.getPodId()).thenReturn(55L); + when(cluster.getId()).thenReturn(33L); + when(vmVolumeMigrationPlanningService.createMappingVolumeAndStoragePool(any(), planCaptor.capture(), eq(requestedMap))) + .thenReturn(plannedMap); + + Map result = service.prepareVmStorageMigration(vm, requestedMap); + + assertEquals(plannedMap, result); + assertEquals(44L, planCaptor.getValue().getDataCenterId()); + assertEquals(Long.valueOf(55L), planCaptor.getValue().getPodId()); + assertEquals(Long.valueOf(33L), planCaptor.getValue().getClusterId()); + assertNull(planCaptor.getValue().getHostId()); + verify(virtualMachineManager).stateTransitTo(vm, Event.StorageMigrationRequested, null); + } + + @Test + public void prepareVmStorageMigrationBuildsZoneDeploymentWhenPoolsHaveNoCluster() throws Exception { + VMInstanceVO vm = mockVm("vm-6"); + StoragePoolVO pool = mock(StoragePoolVO.class); + Map requestedMap = Map.of(11L, 22L); + Map plannedMap = new HashMap<>(); + ArgumentCaptor planCaptor = ArgumentCaptor.forClass(DataCenterDeployment.class); + when(pool.getClusterId()).thenReturn(null); + when(pool.getDataCenterId()).thenReturn(44L); + when(storagePoolDao.findById(22L)).thenReturn(pool); + when(vmVolumeMigrationPlanningService.createMappingVolumeAndStoragePool(any(), planCaptor.capture(), eq(requestedMap))) + .thenReturn(plannedMap); + + Map result = service.prepareVmStorageMigration(vm, requestedMap); + + assertEquals(plannedMap, result); + assertEquals(44L, planCaptor.getValue().getDataCenterId()); + assertNull(planCaptor.getValue().getPodId()); + assertNull(planCaptor.getValue().getClusterId()); + verify(clusterDao, never()).findById(anyLong()); + verify(virtualMachineManager).stateTransitTo(vm, Event.StorageMigrationRequested, null); + } + + @Test + public void prepareVmStorageMigrationRejectsMapThatCannotResolveDataCenter() throws Exception { + VMInstanceVO vm = mockVm("vm-7"); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getClusterId()).thenReturn(33L); + when(storagePoolDao.findById(22L)).thenReturn(pool); + when(clusterDao.findById(33L)).thenReturn(null); + + assertThrows(CloudRuntimeException.class, () -> service.prepareVmStorageMigration(vm, Map.of(11L, 22L))); + + verify(virtualMachineManager, never()).stateTransitTo(eq(vm), eq(Event.StorageMigrationRequested), any()); + } + + @Test + public void migrateThroughHypervisorOrStorageFallsBackToStorageMigrationAndRunsPostCleanup() throws Exception { + VMInstanceVO vm = mockVm("vm-8"); + HostVO sourceHost = mock(HostVO.class); + Map volumeToPool = new HashMap<>(); + when(virtualMachineManager.findClusterAndHostIdForVm(vm, false)).thenReturn(new Pair<>(33L, 44L)); + doReturn(null).when(service).attemptHypervisorMigration(vm, volumeToPool, 44L); + when(volumeMgr.storageMigration(any(), eq(volumeToPool))).thenReturn(true); + when(hostDao.findById(44L)).thenReturn(sourceHost); + doNothing().when(service).postStorageMigrationCleanup(vm, volumeToPool, sourceHost, 33L); + + service.migrateThroughHypervisorOrStorage(vm, volumeToPool); + + verify(volumeMgr).storageMigration(any(), eq(volumeToPool)); + verify(service).postStorageMigrationCleanup(vm, volumeToPool, sourceHost, 33L); + } + + @Test + public void migrateThroughHypervisorOrStorageUsesHypervisorResultsWhenFinalizeCommandsRun() throws Exception { + VMInstanceVO vm = mockVm("vm-9"); + Map volumeToPool = new HashMap<>(); + Answer[] answers = new Answer[] {new Answer(mock(Command.class))}; + when(virtualMachineManager.findClusterAndHostIdForVm(vm, false)).thenReturn(new Pair<>(33L, 44L)); + doReturn(answers).when(service).attemptHypervisorMigration(vm, volumeToPool, 44L); + doNothing().when(service).afterHypervisorMigrationCleanup(vm, volumeToPool, 33L, answers); + + service.migrateThroughHypervisorOrStorage(vm, volumeToPool); + + verify(volumeMgr, never()).storageMigration(any(), any()); + verify(service).afterHypervisorMigrationCleanup(vm, volumeToPool, 33L, answers); + } + + @Test + public void attemptHypervisorMigrationReturnsNullWhenSourceHostMissing() { + VMInstanceVO vm = mockVm("vm-10"); + + assertNull(service.attemptHypervisorMigration(vm, new HashMap<>(), null)); + + verify(hvGuruMgr, never()).getGuru(any()); + } + + @Test + public void markVolumesInPoolThrowsForSingleFailedAnswer() { + VMInstanceVO vm = mockVm("vm-11"); + Answer failedAnswer = new Answer(mock(Command.class), false, "nope"); + + assertThrows(CloudRuntimeException.class, () -> service.markVolumesInPool(vm, new Answer[] {failedAnswer})); + } + + @Test + public void markVolumesInPoolUpdatesPathPoolTypeAndChainInfoFromMigrateAnswer() { + VMInstanceVO vm = mockVm("vm-12"); + VolumeVO volume = mock(VolumeVO.class); + StoragePoolVO pool = mock(StoragePoolVO.class); + VolumeObjectTO result = new VolumeObjectTO(); + result.setId(77L); + result.setUuid("vol-77"); + result.setPath("new/path"); + result.setDataStoreUuid("pool-uuid"); + result.setChainInfo("chain-info"); + MigrateVmToPoolAnswer answer = new MigrateVmToPoolAnswer(new MigrateVmToPoolCommand("vm", List.of(), null, false), List.of(result)); + when(vm.getId()).thenReturn(12L); + when(volumeDao.findUsableVolumesForInstance(12L)).thenReturn(List.of(volume)); + when(volumeDao.findById(77L)).thenReturn(volume); + when(storagePoolDao.findPoolByUUID("pool-uuid")).thenReturn(pool); + when(pool.getId()).thenReturn(88L); + when(pool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); + when(volume.getId()).thenReturn(77L); + + service.markVolumesInPool(vm, new Answer[] {answer}); + + verify(volume).setPath("new/path"); + verify(volume).setPoolId(88L); + verify(volume).setPoolType(Storage.StoragePoolType.NetworkFilesystem); + verify(volume).setChainInfo("chain-info"); + verify(volumeDao).update(77L, volume); + } + + @Test + public void markVolumesInPoolThrowsWhenNoMigrateVmToPoolAnswerExists() { + VMInstanceVO vm = mockVm("vm-13"); + Answer answer = new Answer(mock(Command.class)); + + assertThrows(CloudRuntimeException.class, () -> service.markVolumesInPool(vm, new Answer[] {answer})); + } + + @Test + public void postStorageMigrationCleanupReallocatesNetworkWhenRootPoolPodChanges() throws Exception { + VMInstanceVO vm = mockVm("vm-14"); + Volume rootVolume = mock(Volume.class); + StoragePool rootPool = mock(StoragePool.class); + Map volumeToPool = new HashMap<>(); + volumeToPool.put(rootVolume, rootPool); + when(rootVolume.getVolumeType()).thenReturn(Volume.Type.ROOT); + when(rootPool.getPodId()).thenReturn(22L); + when(vm.getPodIdToDeployIn()).thenReturn(11L); + when(vm.getDataCenterId()).thenReturn(33L); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + ArgumentCaptor planCaptor = ArgumentCaptor.forClass(DataCenterDeployment.class); + + service.postStorageMigrationCleanup(vm, volumeToPool, mock(HostVO.class), 44L); + + verify(networkMgr).reallocate(any(), planCaptor.capture()); + assertEquals(33L, planCaptor.getValue().getDataCenterId()); + assertEquals(Long.valueOf(22L), planCaptor.getValue().getPodId()); + verify(vm).setLastHostId(null); + verify(vm).setPodIdToDeployIn(22L); + } + + @Test + public void afterStorageMigrationVmwareVMCleanupUnregistersVmWhenVmwareDatacenterChanges() { + VMInstanceVO vm = mockVm("vm-15"); + StoragePool destPool = mock(StoragePool.class); + HostVO sourceHost = mock(HostVO.class); + when(destPool.getClusterId()).thenReturn(22L); + when(clusterDetailsDao.getVmwareDcName(11L)).thenReturn("src-dc"); + when(clusterDetailsDao.getVmwareDcName(22L)).thenReturn("dest-dc"); + doNothing().when(service).removeStaleVmFromSource(vm, sourceHost); + + service.afterStorageMigrationVmwareVMCleanup(destPool, vm, sourceHost, 11L); + + verify(service).removeStaleVmFromSource(vm, sourceHost); + } + + @Test + public void removeStaleVmFromSourceWrapsAgentSendFailure() throws Exception { + VMInstanceVO vm = mockVm("vm-16"); + HostVO sourceHost = mock(HostVO.class); + when(sourceHost.getId()).thenReturn(44L); + doThrow(new AgentUnavailableException(44L)).when(agentMgr).send(eq(44L), any(Command.class)); + + assertThrows(CloudRuntimeException.class, () -> service.removeStaleVmFromSource(vm, sourceHost)); + } + + @Test + public void checkDestinationForTagsThrowsWhenDiskOfferingTagsDoNotMatchPoolTags() { + VMInstanceVO vm = mockVm("vm-17"); + StoragePool destPool = mock(StoragePool.class); + VolumeVO volume = mock(VolumeVO.class); + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(vm.getId()).thenReturn(17L); + when(destPool.getId()).thenReturn(88L); + when(destPool.getName()).thenReturn("pool-88"); + when(volumeDao.findUsableVolumesForInstance(17L)).thenReturn(List.of(volume)); + when(storageMgr.getStoragePoolTagList(88L)).thenReturn(List.of("silver")); + when(volume.getDiskOfferingId()).thenReturn(99L); + when(volume.getName()).thenReturn("volume-99"); + when(diskOfferingDao.findById(99L)).thenReturn(diskOffering); + when(diskOffering.getTags()).thenReturn("gold"); + + assertThrows(CloudRuntimeException.class, () -> service.checkDestinationForTags(destPool, vm)); + } + + @Test + public void matchesOfSortsPreservesExistingTruthTable() { + List nothing = null; + List empty = new ArrayList<>(); + List tag = Arrays.asList("bla"); + List tags = Arrays.asList("bla", "blob"); + List others = Arrays.asList("bla", "blieb"); + List three = Arrays.asList("bla", "blob", "blieb"); + + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(tag, tags)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(tag, others)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(nothing, tags)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(empty, tag)); + assertFalse(VmOfflineStorageMigrationServiceImpl.matches(tags, tag)); + assertFalse(VmOfflineStorageMigrationServiceImpl.matches(tag, nothing)); + assertFalse(VmOfflineStorageMigrationServiceImpl.matches(tag, empty)); + assertFalse(VmOfflineStorageMigrationServiceImpl.matches(tags, others)); + assertFalse(VmOfflineStorageMigrationServiceImpl.matches(others, tags)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(nothing, three)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(empty, three)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(tag, three)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(tags, three)); + assertTrue(VmOfflineStorageMigrationServiceImpl.matches(others, three)); + } + + private VMInstanceVO mockVm(String uuid) { + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getUuid()).thenReturn(uuid); + when(vm.getInstanceName()).thenReturn(uuid + "-name"); + when(vm.getHypervisorType()).thenReturn(HypervisorType.VMware); + return vm; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmPowerStateSyncManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmPowerStateSyncManagerImplTest.java new file mode 100644 index 000000000000..4dc692aea12f --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmPowerStateSyncManagerImplTest.java @@ -0,0 +1,426 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.Date; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.utils.cache.SingleCache; + +import com.cloud.alert.AlertManager; +import com.cloud.event.ActionEventUtils; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.vm.VirtualMachine.PowerState; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmPowerStateSyncManagerImplTest { + + @InjectMocks + private VmPowerStateSyncManagerImpl manager; + + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private VmWorkJobDao workJobDao; + @Mock + private HighAvailabilityManager haMgr; + @Mock + private HostDao hostDao; + @Mock + private AlertManager alertMgr; + @Mock + private VmStateMachineActions vmStateMachineActions; + + private static final long VM_ID = 42L; + private static final long HOST_ID = 100L; + + @Before + public void setUp() { + // Supply an empty-cache by default; override per-test as needed + SingleCache> emptyCache = new SingleCache<>(10, Collections::emptyList); + ReflectionTestUtils.setField(manager, "vmIdsInProgressCache", emptyCache); + ReflectionTestUtils.setField(manager, "syncTransitioningVmPowerState", true); + } + + // ────────────────────────────────────────────────────────────────── + // handlePowerStateReport tests + // ────────────────────────────────────────────────────────────────── + + @Test + public void handlePowerStateReport_nullVm_logsAndReturns() throws Exception { + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID)) + .thenReturn(Collections.emptyList()); + when(haMgr.hasPendingHaWork(VM_ID)).thenReturn(false); + when(vmInstanceDao.findById(VM_ID)).thenReturn(null); + + manager.handlePowerStateReport(VM_ID); + + verify(vmStateMachineActions, never()).stateTransitTo(any(), any(), any()); + verify(alertMgr, never()).sendAlert(any(), anyLong(), any(), anyString(), anyString()); + } + + @Test + public void handlePowerStateReport_pendingWorkJob_resetsCounters() throws Exception { + VmWorkJobVO pendingJob = mock(VmWorkJobVO.class); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID)) + .thenReturn(List.of(pendingJob)); + + manager.handlePowerStateReport(VM_ID); + + verify(vmInstanceDao).resetVmPowerStateTracking(VM_ID); + verify(vmStateMachineActions, never()).stateTransitTo(any(), any(), any()); + } + + @Test + public void handlePowerStateReport_pendingHaWork_resetsCounters() { + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID)) + .thenReturn(Collections.emptyList()); + when(haMgr.hasPendingHaWork(VM_ID)).thenReturn(true); + + manager.handlePowerStateReport(VM_ID); + + verify(vmInstanceDao).resetVmPowerStateTracking(VM_ID); + } + + @Test + public void handlePowerStateReport_routesPowerOnToOnHandler() throws Exception { + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerOn); + setupCleanJobsAndHa(); + when(vmInstanceDao.findById(VM_ID)).thenReturn(vm); + + manager.handlePowerStateReport(VM_ID); + + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOnReport), any()); + } + + @Test + public void handlePowerStateReport_routesPowerOffToOffHandler() throws Exception { + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerOff); + when(vm.isHaEnabled()).thenReturn(false); + org.mockito.Mockito.lenient().when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + setupCleanJobsAndHa(); + when(vmInstanceDao.findById(VM_ID)).thenReturn(vm); + when(vmStateMachineActions.sendStop(any(), any(), anyBoolean(), anyBoolean())).thenReturn(true); + + try (MockedStatic mocked = mockStatic(ActionEventUtils.class)) { + manager.handlePowerStateReport(VM_ID); + } + + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOffReport), eq(null)); + } + + @Test + public void handlePowerStateReport_routesPowerReportMissingToOffHandler() throws Exception { + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerReportMissing); + when(vm.isHaEnabled()).thenReturn(false); + org.mockito.Mockito.lenient().when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + setupCleanJobsAndHa(); + when(vmInstanceDao.findById(VM_ID)).thenReturn(vm); + + try (MockedStatic mocked = mockStatic(ActionEventUtils.class)) { + manager.handlePowerStateReport(VM_ID); + } + + // PowerReportMissing path: releaseVmResources called, sendStop NOT called + verify(vmStateMachineActions).releaseVmResources(any(), eq(true)); + verify(vmStateMachineActions, never()).sendStop(any(), any(), anyBoolean(), anyBoolean()); + } + + // ────────────────────────────────────────────────────────────────── + // handlePowerOnReportWithNoPendingJobsOnVM tests + // ────────────────────────────────────────────────────────────────── + + @Test + public void handlePowerOnReport_startingState_transitionsAndAlerts() throws Exception { + VMInstanceVO vm = mockVm(State.Starting, PowerState.PowerOn); + + manager.handlePowerOnReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOnReport), any()); + verify(alertMgr).sendAlert(eq(AlertManager.AlertType.ALERT_TYPE_SYNC), anyLong(), any(), anyString(), anyString()); + } + + @Test + public void handlePowerOnReport_runningSameHost_singleTransitionNoAlert() throws Exception { + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerOn); + when(vm.getHostId()).thenReturn(HOST_ID); + when(vm.getPowerHostId()).thenReturn(HOST_ID); + + manager.handlePowerOnReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOnReport), eq(HOST_ID)); + verify(alertMgr, never()).sendAlert(any(), anyLong(), any(), anyString(), anyString()); + } + + @Test + public void handlePowerOnReport_stoppedState_transitionsAlertsAndEmitsActionEvent() throws Exception { + VMInstanceVO vm = mockVm(State.Stopped, PowerState.PowerOn); + + try (MockedStatic mocked = mockStatic(ActionEventUtils.class)) { + manager.handlePowerOnReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOnReport), any()); + verify(alertMgr).sendAlert(eq(AlertManager.AlertType.ALERT_TYPE_SYNC), anyLong(), any(), anyString(), anyString()); + mocked.verify(() -> ActionEventUtils.onActionEvent(anyLong(), anyLong(), anyLong(), anyString(), anyString(), anyLong(), anyString())); + } + } + + @Test + public void handlePowerOnReport_destroyedState_noop() throws Exception { + VMInstanceVO vm = mockVm(State.Destroyed, PowerState.PowerOn); + + manager.handlePowerOnReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions, never()).stateTransitTo(any(), any(), any()); + verify(alertMgr, never()).sendAlert(any(), anyLong(), any(), anyString(), anyString()); + } + + // ────────────────────────────────────────────────────────────────── + // handlePowerOffReportWithNoPendingJobsOnVM tests + // ────────────────────────────────────────────────────────────────── + + @Test + public void handlePowerOffReport_haEnabledRunningKvm_schedulesRestart() throws Exception { + // ForceHA ConfigKey defaults to false in tests; vm.isHaEnabled()=true satisfies the HA condition + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerOff); + when(vm.isHaEnabled()).thenReturn(true); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(haMgr.hasPendingHaWork(VM_ID)).thenReturn(false); + + try (MockedStatic mocked = mockStatic(ActionEventUtils.class)) { + manager.handlePowerOffReportWithNoPendingJobsOnVM(vm); + } + + verify(haMgr).scheduleRestart(vm, true); + verify(vmStateMachineActions, never()).stateTransitTo(any(), any(), any()); //NOSONAR + } + + @Test + public void handlePowerOffReport_powerOff_sendsStopThenReleasesAndTransitions() throws Exception { + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerOff); + when(vm.isHaEnabled()).thenReturn(false); + org.mockito.Mockito.lenient().when(vm.getHypervisorType()).thenReturn(HypervisorType.XenServer); + when(vmStateMachineActions.getVmGuru(vm)).thenReturn(mock(VirtualMachineGuru.class)); + when(vmStateMachineActions.sendStop(any(), any(), eq(true), eq(true))).thenReturn(true); + + try (MockedStatic mocked = mockStatic(ActionEventUtils.class)) { + manager.handlePowerOffReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions).sendStop(any(), any(), eq(true), eq(true)); + verify(vmStateMachineActions).releaseVmResources(any(), eq(true)); + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOffReport), eq(null)); + verify(alertMgr).sendAlert(eq(AlertManager.AlertType.ALERT_TYPE_SYNC), anyLong(), any(), anyString(), anyString()); + } + } + + @Test + public void handlePowerOffReport_powerReportMissing_releasesOnly() throws Exception { + VMInstanceVO vm = mockVm(State.Running, PowerState.PowerReportMissing); + when(vm.isHaEnabled()).thenReturn(false); + org.mockito.Mockito.lenient().when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + + try (MockedStatic mocked = mockStatic(ActionEventUtils.class)) { + manager.handlePowerOffReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions, never()).sendStop(any(), any(), anyBoolean(), anyBoolean()); + verify(vmStateMachineActions).releaseVmResources(any(), eq(true)); + verify(vmStateMachineActions).stateTransitTo(eq(vm), eq(VirtualMachine.Event.FollowAgentPowerOffReport), eq(null)); + } + } + + @Test + public void handlePowerOffReport_destroyedState_noop() throws Exception { + VMInstanceVO vm = mockVm(State.Destroyed, PowerState.PowerOff); + + manager.handlePowerOffReportWithNoPendingJobsOnVM(vm); + + verify(vmStateMachineActions, never()).stateTransitTo(any(), any(), any()); + verify(haMgr, never()).scheduleRestart(any(), anyBoolean()); + verify(alertMgr, never()).sendAlert(any(), anyLong(), any(), anyString(), anyString()); + } + + // ────────────────────────────────────────────────────────────────── + // scanStalledVMInTransitionStateOnUpHost tests + // ────────────────────────────────────────────────────────────────── + + @Test + public void scanStalledVMInTransitionStateOnUpHost_syncDisabled_noops() { + ReflectionTestUtils.setField(manager, "syncTransitioningVmPowerState", false); + + manager.scanStalledVMInTransitionStateOnUpHost(HOST_ID); + + verify(hostDao, never()).isHostUp(anyLong()); + } + + @Test + public void scanStalledVMInTransitionStateOnUpHost_hostDown_noops() { + when(hostDao.isHostUp(HOST_ID)).thenReturn(false); + + manager.scanStalledVMInTransitionStateOnUpHost(HOST_ID); + + verify(vmInstanceDao, never()).listByHostAndState(anyLong(), any()); + } + + @Test + public void scanStalledVMInTransitionStateOnUpHost_stalledVms_treatedAsPoweredOff() throws Exception { + when(hostDao.isHostUp(HOST_ID)).thenReturn(true); + + // Build a stale VM: powerStateUpdateTime well before the cutTime threshold + VMInstanceVO staleVm = mockVm(State.Starting, PowerState.PowerOff); + long oldTime = System.currentTimeMillis() - 200_000L; // older than 2 * 60s default threshold + when(staleVm.getPowerStateUpdateTime()).thenReturn(new Date(oldTime)); + + when(vmInstanceDao.listByHostAndState(eq(HOST_ID), any(), any(), any())) + .thenReturn(List.of(staleVm)); + + // Use spy and stub the inner handler to avoid ActionEventUtils NPE + VmPowerStateSyncManagerImpl spy = spy(manager); + org.mockito.Mockito.doNothing().when(spy).handlePowerOffReportWithNoPendingJobsOnVM(staleVm); + + spy.scanStalledVMInTransitionStateOnUpHost(HOST_ID); + + verify(spy).handlePowerOffReportWithNoPendingJobsOnVM(staleVm); + } + + @Test + public void scanStalledVMInTransitionStateOnUpHost_recentReport_routesByPowerState() throws Exception { + when(hostDao.isHostUp(HOST_ID)).thenReturn(true); + + long recentTime = System.currentTimeMillis() + 200_000L; // well into the future = recent + + VMInstanceVO powerOnVm = mockVm(State.Starting, PowerState.PowerOn); + when(powerOnVm.getPowerStateUpdateTime()).thenReturn(new Date(recentTime)); + + VMInstanceVO powerOffVm = mockVm(State.Stopping, PowerState.PowerOff); + when(powerOffVm.getPowerStateUpdateTime()).thenReturn(new Date(recentTime)); + + when(vmInstanceDao.listByHostAndState(eq(HOST_ID), any(), any(), any())) + .thenReturn(List.of(powerOnVm, powerOffVm)); + + VmPowerStateSyncManagerImpl spy = spy(manager); + org.mockito.Mockito.doNothing().when(spy).handlePowerOnReportWithNoPendingJobsOnVM(powerOnVm); + org.mockito.Mockito.doNothing().when(spy).handlePowerOffReportWithNoPendingJobsOnVM(powerOffVm); + + spy.scanStalledVMInTransitionStateOnUpHost(HOST_ID); + + verify(spy).handlePowerOnReportWithNoPendingJobsOnVM(powerOnVm); + verify(spy).handlePowerOffReportWithNoPendingJobsOnVM(powerOffVm); + } + + // ────────────────────────────────────────────────────────────────── + // scanStalledVMInTransitionStateOnDisconnectedHosts tests + // ────────────────────────────────────────────────────────────────── + + @Test + public void scanStalledVMInTransitionStateOnDisconnectedHosts_emitsAlerts() { + VmPowerStateSyncManagerImpl spy = spy(manager); + + VMInstanceVO vm1 = mockVm(State.Starting, PowerState.PowerUnknown); + VMInstanceVO vm2 = mockVm(State.Stopping, PowerState.PowerUnknown); + + // Stub the raw-JDBC helper to avoid DB access + org.mockito.Mockito.doReturn(List.of(VM_ID, VM_ID + 1)) + .when(spy).listStalledVMInTransitionStateOnDisconnectedHosts(any(Date.class)); + when(vmInstanceDao.findById(VM_ID)).thenReturn(vm1); + when(vmInstanceDao.findById(VM_ID + 1)).thenReturn(vm2); + + spy.scanStalledVMInTransitionStateOnDisconnectedHosts(); + + verify(alertMgr, times(2)).sendAlert( + eq(AlertManager.AlertType.ALERT_TYPE_SYNC), + anyLong(), any(), anyString(), anyString()); + } + + // ────────────────────────────────────────────────────────────────── + // getApiCommandResourceTypeForVm branches + // ────────────────────────────────────────────────────────────────── + + @Test + public void getApiCommandResourceTypeForVm_branches() { + assertEquals(ApiCommandResourceType.DomainRouter, + manager.getApiCommandResourceTypeForVm(mockVmOfType(VirtualMachine.Type.DomainRouter))); + assertEquals(ApiCommandResourceType.ConsoleProxy, + manager.getApiCommandResourceTypeForVm(mockVmOfType(VirtualMachine.Type.ConsoleProxy))); + assertEquals(ApiCommandResourceType.SystemVm, + manager.getApiCommandResourceTypeForVm(mockVmOfType(VirtualMachine.Type.SecondaryStorageVm))); + assertEquals(ApiCommandResourceType.VirtualMachine, + manager.getApiCommandResourceTypeForVm(mockVmOfType(VirtualMachine.Type.User))); + } + + // ────────────────────────────────────────────────────────────────── + // Helpers + // ────────────────────────────────────────────────────────────────── + + private VMInstanceVO mockVm(State state, PowerState powerState) { + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getState()).thenReturn(state); + when(vm.getPowerState()).thenReturn(powerState); + when(vm.getDataCenterId()).thenReturn(1L); + when(vm.getPodIdToDeployIn()).thenReturn(1L); + when(vm.getHostName()).thenReturn("test-host"); + when(vm.getInstanceName()).thenReturn("i-1-VM"); + when(vm.getDomainId()).thenReturn(1L); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(vm.getPowerHostId()).thenReturn(HOST_ID); + when(vm.getPowerStateUpdateTime()).thenReturn(new Date()); + return vm; + } + + private VirtualMachine mockVmOfType(VirtualMachine.Type type) { + VirtualMachine vm = mock(VirtualMachine.class); + when(vm.getType()).thenReturn(type); + return vm; + } + + private void setupCleanJobsAndHa() { + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID)) + .thenReturn(Collections.emptyList()); + when(haMgr.hasPendingHaWork(VM_ID)).thenReturn(false); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmRebootOrchestrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmRebootOrchestrationServiceImplTest.java new file mode 100644 index 000000000000..884ca1141b4e --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmRebootOrchestrationServiceImplTest.java @@ -0,0 +1,227 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.agent.manager.Commands; +import com.cloud.dc.DataCenter; +import com.cloud.dc.Pod; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.security.SecurityGroupManager; +import com.cloud.org.Cluster; +import com.cloud.resource.ResourceManager; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.snapshot.VMSnapshotManager; + +@RunWith(MockitoJUnitRunner.class) +public class VmRebootOrchestrationServiceImplTest { + + private static final String VM_UUID = "vm-uuid"; + private static final long VM_ID = 42L; + private static final long HOST_ID = 7L; + private static final long DATA_CENTER_ID = 8L; + private static final long CLUSTER_ID = 9L; + private static final long POD_ID = 10L; + + @Spy + @InjectMocks + private VmRebootOrchestrationServiceImpl service = new VmRebootOrchestrationServiceImpl(); + + @Mock + private AgentManager agentMgr; + @Mock + private VMInstanceDao vmDao; + @Mock + private VMSnapshotManager vmSnapshotMgr; + @Mock + private EntityManager entityMgr; + @Mock + private HostDao hostDao; + @Mock + private VmWorkJobQueueService vmWorkJobQueueService; + @Mock + private VmCommandSpecPostProcessingService vmCommandSpecPostProcessingService; + @Mock + private VmExternalProvisioningManager vmExternalProvisioningManager; + @Mock + private SecurityGroupManager securityGroupManager; + @Mock + private ResourceManager resourceMgr; + @Mock + private VMInstanceVO vm; + @Mock + private HostVO host; + @Mock + private VirtualMachineTO vmTo; + + @Test + public void advanceRebootDispatchesThroughJobQueueWhenNotAlreadyInWorkJob() + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + Outcome outcome = mock(Outcome.class); + Map params = Map.of(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(false); + when(vmWorkJobQueueService.rebootVmThroughJobQueue(VM_UUID, params)).thenReturn(outcome); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.advanceReboot(VM_UUID, params); + } + + verify(vmWorkJobQueueService).retrieveVmFromJobOutcome(outcome, VM_UUID, "rebootVm"); + verify(vmWorkJobQueueService).retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } + + @Test + public void advanceRebootCreatesAndExpungesPlaceholderWhenAlreadyInWorkJob() + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + VmWorkJobVO placeholder = new VmWorkJobVO(""); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(true); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vmWorkJobQueueService.createPlaceHolderWork(VM_ID)).thenReturn(placeholder); + doNothing().when(service).orchestrateReboot(VM_UUID, null); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.advanceReboot(VM_UUID, null); + } + + verify(service).orchestrateReboot(VM_UUID, null); + verify(vmWorkJobQueueService).expungePlaceHolderWork(placeholder); + } + + @Test + public void rebootWrapsConcurrentOperationException() throws Exception { + doAnswer(invocation -> { + throw new ConcurrentOperationException("busy"); + }).when(service).advanceReboot(VM_UUID, null); + + assertThrows(CloudRuntimeException.class, () -> service.reboot(VM_UUID, null)); + } + + @Test + public void orchestrateRebootSendsCommandAndSchedulesSecurityGroupRefresh() throws Exception { + Map params = Map.of(VirtualMachineProfile.Param.BootIntoSetup, Boolean.TRUE); + prepareVmAndHost(); + when(securityGroupManager.isVmSecurityGroupEnabled(VM_ID)).thenReturn(true); + doReturn(vmTo).when(service).getVmTO(VM_ID); + doReturn(false).when(service).getExecuteInSequence(HypervisorType.KVM); + doAnswer(invocation -> { + Commands cmds = invocation.getArgument(1); + RebootCommand command = cmds.getCommand(RebootCommand.class); + cmds.setAnswers(new Answer[] {new RebootAnswer(command, "ok", true)}); + return null; + }).when(agentMgr).send(eq(HOST_ID), any(Commands.class)); + + service.orchestrateReboot(VM_UUID, params); + + ArgumentCaptor rebootCommandCaptor = ArgumentCaptor.forClass(RebootCommand.class); + verify(vmCommandSpecPostProcessingService).setEnterSetupMode(vmTo, params); + verify(vmExternalProvisioningManager).updateRebootCommandWithExternalDetails(eq(host), eq(vmTo), rebootCommandCaptor.capture()); + assertSame(vmTo, rebootCommandCaptor.getValue().getVirtualMachine()); + verify(securityGroupManager).scheduleRulesetUpdateToHosts(eq(List.of(VM_ID)), eq(true), eq(null)); + verify(resourceMgr, never()).updateGPUDetailsForVmStart(anyLong(), anyLong(), any()); + } + + @Test + public void orchestrateRebootRejectsActiveSnapshotTasks() throws Exception { + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vmSnapshotMgr.hasActiveVMSnapshotTasks(VM_ID)).thenReturn(true); + + assertThrows(CloudRuntimeException.class, () -> service.orchestrateReboot(VM_UUID, null)); + + verify(agentMgr, never()).send(any(), any(Commands.class)); + } + + @Test + public void orchestrateRebootFailsWhenHostCannotBeResolved() throws Exception { + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getHostId()).thenReturn(HOST_ID); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> service.orchestrateReboot(VM_UUID, null)); + + org.junit.Assert.assertEquals("Unable to retrieve host with id " + HOST_ID, exception.getMessage()); + verify(agentMgr, never()).send(any(), any(Commands.class)); + } + + private void prepareVmAndHost() { + DataCenter dataCenter = mock(DataCenter.class); + Cluster cluster = mock(Cluster.class); + Pod pod = mock(Pod.class); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getHostId()).thenReturn(HOST_ID); + when(vm.getDataCenterId()).thenReturn(DATA_CENTER_ID); + when(vm.getInstanceName()).thenReturn("i-42-VM"); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(hostDao.findById(HOST_ID)).thenReturn(host); + when(host.getId()).thenReturn(HOST_ID); + when(host.getClusterId()).thenReturn(CLUSTER_ID); + when(host.getPodId()).thenReturn(POD_ID); + when(entityMgr.findById(DataCenter.class, DATA_CENTER_ID)).thenReturn(dataCenter); + when(entityMgr.findById(Cluster.class, CLUSTER_ID)).thenReturn(cluster); + when(entityMgr.findById(Pod.class, POD_ID)).thenReturn(pod); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmScaleReconfigurationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmScaleReconfigurationServiceImplTest.java new file mode 100644 index 000000000000..06ffbe443130 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmScaleReconfigurationServiceImplTest.java @@ -0,0 +1,336 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ScaleVmCommand; +import com.cloud.alert.AlertManager; +import com.cloud.capacity.CapacityManager; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeployDestination; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.offering.ServiceOffering; +import com.cloud.org.Cluster; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmScaleReconfigurationServiceImplTest { + + private static final String VM_UUID = "vm-uuid"; + private static final long VM_ID = 42L; + private static final long SRC_HOST_ID = 7L; + private static final long DST_HOST_ID = 8L; + private static final long OLD_OFFERING_ID = 11L; + private static final long NEW_OFFERING_ID = 12L; + private static final long DATA_CENTER_ID = 13L; + private static final long POD_ID = 14L; + private static final long SRC_CLUSTER_ID = 15L; + private static final long DST_CLUSTER_ID = 16L; + + @Spy + @InjectMocks + private VmScaleReconfigurationServiceImpl service = new VmScaleReconfigurationServiceImpl(); + + @Mock + private VMInstanceDao vmDao; + @Mock + private ServiceOfferingDao offeringDao; + @Mock + private HostDao hostDao; + @Mock + private DeploymentPlanningManager dpMgr; + @Mock + private AgentManager agentMgr; + @Mock + private NetworkOrchestrationService networkMgr; + @Mock + private VolumeOrchestrationService volumeMgr; + @Mock + private ItWorkDao workDao; + @Mock + private AlertManager alertMgr; + @Mock + private HypervisorGuruManager hvGuruMgr; + @Mock + private UserVmManager userVmMgr; + @Mock + private CapacityManager capacityMgr; + @Mock + private VmWorkJobQueueService vmWorkJobQueueService; + @Mock + private VmServiceOfferingUpgradeManager vmServiceOfferingUpgradeManager; + @Mock + private VmScaleReconfigurationActions vmScaleReconfigurationActions; + @Mock + private VMInstanceVO vm; + @Mock + private HostVO sourceHost; + @Mock + private HostVO destHost; + @Mock + private DeployDestination dest; + @Mock + private ServiceOfferingVO baseOffering; + @Mock + private ServiceOfferingVO computedOffering; + + @Test + public void findHostAndMigrateBuildsDynamicOfferingPlanAndDelegatesToScaleMigration() + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + Map customParameters = Map.of("cpuNumber", "4"); + DeploymentPlanner.ExcludeList excludes = new DeploymentPlanner.ExcludeList(); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getHostId()).thenReturn(SRC_HOST_ID); + when(vm.getServiceOfferingId()).thenReturn(OLD_OFFERING_ID); + when(vm.getUuid()).thenReturn(VM_UUID); + when(offeringDao.findById(NEW_OFFERING_ID)).thenReturn(baseOffering); + when(baseOffering.isDynamic()).thenReturn(true); + when(offeringDao.getComputeOffering(baseOffering, customParameters)).thenReturn(computedOffering); + when(hostDao.findById(SRC_HOST_ID)).thenReturn(sourceHost); + when(sourceHost.getDataCenterId()).thenReturn(DATA_CENTER_ID); + when(sourceHost.getPodId()).thenReturn(POD_ID); + when(sourceHost.getClusterId()).thenReturn(SRC_CLUSTER_ID); + when(dpMgr.planDeployment(any(VirtualMachineProfile.class), any(DataCenterDeployment.class), eq(excludes), isNull())).thenReturn(dest); + when(dest.getHost()).thenReturn(destHost); + when(destHost.getId()).thenReturn(DST_HOST_ID); + doNothing().when(service).migrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID); + + service.findHostAndMigrate(VM_UUID, NEW_OFFERING_ID, customParameters, excludes); + + verify(baseOffering).setDynamicFlag(true); + verify(offeringDao).getComputeOffering(baseOffering, customParameters); + verify(vm).setServiceOfferingId(NEW_OFFERING_ID); + verify(service).migrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID); + } + + @Test + public void migrateForScaleDispatchesThroughJobQueueWhenNotAlreadyInWorkJob() + throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + Outcome outcome = mock(Outcome.class); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(false); + when(vmWorkJobQueueService.migrateVmForScaleThroughJobQueue(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID)).thenReturn(outcome); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.migrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID); + } + + verify(vmWorkJobQueueService).retrieveVmFromJobOutcome(outcome, VM_UUID, "migrateVmForScale"); + verify(vmWorkJobQueueService).retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } + + @Test + public void migrateForScaleCreatesAndExpungesPlaceholderWhenAlreadyInWorkJob() + throws ResourceUnavailableException, ConcurrentOperationException { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + VmWorkJobVO placeholder = new VmWorkJobVO(""); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(true); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vmWorkJobQueueService.createPlaceHolderWork(VM_ID)).thenReturn(placeholder); + doNothing().when(service).orchestrateMigrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.migrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID); + } + + verify(service).orchestrateMigrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID); + verify(vmWorkJobQueueService).expungePlaceHolderWork(placeholder); + } + + @Test + public void orchestrateMigrateForScaleRejectsDestinationOnDifferentCluster() + throws ResourceUnavailableException, ConcurrentOperationException { + Cluster destCluster = mock(Cluster.class); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(dest.getHost()).thenReturn(destHost); + when(destHost.getId()).thenReturn(DST_HOST_ID); + when(hostDao.findById(SRC_HOST_ID)).thenReturn(sourceHost); + when(hostDao.findById(DST_HOST_ID)).thenReturn(destHost); + when(sourceHost.getClusterId()).thenReturn(SRC_CLUSTER_ID); + when(dest.getCluster()).thenReturn(destCluster); + when(destCluster.getId()).thenReturn(DST_CLUSTER_ID); + + assertThrows(CloudRuntimeException.class, + () -> service.orchestrateMigrateForScale(VM_UUID, SRC_HOST_ID, dest, OLD_OFFERING_ID)); + + verifyNoInteractions(networkMgr, volumeMgr, vmScaleReconfigurationActions); + } + + @Test + public void reConfigureVmDispatchesThroughJobQueueWhenNotAlreadyInWorkJob() + throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + Outcome outcome = mock(Outcome.class); + ServiceOffering oldOffering = mock(ServiceOffering.class); + ServiceOffering newOffering = mock(ServiceOffering.class); + Map customParameters = Map.of("memory", "4096"); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(false); + when(vmWorkJobQueueService.reconfigureVmThroughJobQueue(VM_UUID, oldOffering, newOffering, customParameters, true)).thenReturn(outcome); + when(vmWorkJobQueueService.retrieveVmFromJobOutcome(outcome, VM_UUID, "reconfigureVm")).thenReturn(vm); + + VMInstanceVO result; + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + result = service.reConfigureVm(VM_UUID, oldOffering, newOffering, customParameters, true); + } + + assertSame(vm, result); + verify(vmWorkJobQueueService).retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } + + @Test + public void orchestrateReConfigureVmSendsScaleCommandAndUpdatesCapacityOnSameHost() throws Exception { + ServiceOffering oldOffering = mock(ServiceOffering.class); + ServiceOffering newOffering = mock(ServiceOffering.class); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getHostId()).thenReturn(SRC_HOST_ID); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getUuid()).thenReturn(VM_UUID); + when(vm.getInstanceName()).thenReturn("i-2-42-VM"); + when(vm.getType()).thenReturn(VirtualMachine.Type.User); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(vm.isDisplayVm()).thenReturn(true); + when(hostDao.findById(SRC_HOST_ID)).thenReturn(sourceHost); + when(sourceHost.getClusterId()).thenReturn(SRC_CLUSTER_ID); + when(oldOffering.getId()).thenReturn(OLD_OFFERING_ID); + when(newOffering.getId()).thenReturn(NEW_OFFERING_ID); + when(newOffering.getCpu()).thenReturn(4); + when(newOffering.getSpeed()).thenReturn(1000); + when(newOffering.getRamSize()).thenReturn(2048); + when(newOffering.getLimitCpuUse()).thenReturn(false); + when(vmScaleReconfigurationActions.getNodeId()).thenReturn(99L); + when(agentMgr.send(eq(SRC_HOST_ID), any(ScaleVmCommand.class))).thenReturn(new Answer(null, true, null)); + + VMInstanceVO result = service.orchestrateReConfigureVm(VM_UUID, oldOffering, newOffering, true); + + ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(ScaleVmCommand.class); + assertSame(vm, result); + verify(agentMgr).send(eq(SRC_HOST_ID), commandCaptor.capture()); + assertEquals(VM_ID, commandCaptor.getValue().getVirtualMachine().getId()); + assertEquals(VM_UUID, commandCaptor.getValue().getVirtualMachine().getUuid()); + assertEquals(VirtualMachine.Type.User, commandCaptor.getValue().getVirtualMachine().getType()); + verify(vmServiceOfferingUpgradeManager).upgradeVmDb(VM_ID, newOffering, oldOffering); + verify(userVmMgr).generateUsageEvent(vm, true, EventTypes.EVENT_VM_DYNAMIC_SCALE); + verify(capacityMgr).releaseVmCapacity(vm, false, false, SRC_HOST_ID); + verify(capacityMgr).allocateVmCapacity(vm, false); + } + + @Test + public void findHostAndMigrateThrowsWhenVmNotFound() { + when(vmDao.findByUuid(VM_UUID)).thenReturn(null); + + assertThrows(CloudRuntimeException.class, + () -> service.findHostAndMigrate(VM_UUID, NEW_OFFERING_ID, Map.of(), new DeploymentPlanner.ExcludeList())); + } + + @Test + public void findHostAndMigrateThrowsWhenHostIdIsNull() + throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getHostId()).thenReturn(null); + when(vm.getServiceOfferingId()).thenReturn(OLD_OFFERING_ID); + when(offeringDao.findById(NEW_OFFERING_ID)).thenReturn(baseOffering); + when(baseOffering.isDynamic()).thenReturn(false); + + assertThrows(CloudRuntimeException.class, + () -> service.findHostAndMigrate(VM_UUID, NEW_OFFERING_ID, Map.of(), new DeploymentPlanner.ExcludeList())); + } + + @Test + public void reConfigureVmCreatesAndExpungesPlaceholderWhenInWorkJob() throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + VmWorkJobVO placeholder = new VmWorkJobVO(""); + ServiceOffering oldOffering = mock(ServiceOffering.class); + ServiceOffering newOffering = mock(ServiceOffering.class); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(true); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vmWorkJobQueueService.createPlaceHolderWork(VM_ID)).thenReturn(placeholder); + doReturn(vm).when(service).orchestrateReConfigureVm(VM_UUID, oldOffering, newOffering, false); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.reConfigureVm(VM_UUID, oldOffering, newOffering, Map.of(), false); + } + + verify(vmWorkJobQueueService).createPlaceHolderWork(VM_ID); + verify(service).orchestrateReConfigureVm(VM_UUID, oldOffering, newOffering, false); + verify(vmWorkJobQueueService).expungePlaceHolderWork(placeholder); + } + + @Test + public void customOfferingDetailWrappersDelegateToUpgradeManager() { + ServiceOffering offering = mock(ServiceOffering.class); + + service.removeCustomOfferingDetails(VM_ID); + service.saveCustomOfferingDetails(VM_ID, offering); + + verify(vmServiceOfferingUpgradeManager).removeCustomOfferingDetails(VM_ID); + verify(vmServiceOfferingUpgradeManager).saveCustomOfferingDetails(VM_ID, offering); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmServiceOfferingUpgradeManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmServiceOfferingUpgradeManagerImplTest.java new file mode 100644 index 000000000000..70d79b0fba76 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmServiceOfferingUpgradeManagerImplTest.java @@ -0,0 +1,598 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.event.UsageEventVO; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.offering.DiskOffering; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.ScopeType; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmServiceOfferingUpgradeManagerImplTest { + + @Mock private VolumeDao volumeDao; + @Mock private PrimaryDataStoreDao storagePoolDao; + @Mock private VMInstanceDao vmInstanceDao; + @Mock private VMInstanceDetailsDao vmInstanceDetailsDao; + @Mock private VMTemplateDao templateDao; + @Mock private ServiceOfferingDao serviceOfferingDao; + @Mock private DiskOfferingDao diskOfferingDao; + @Mock private EntityManager entityMgr; + @Mock private UserVmManager userVmManager; + + @InjectMocks + private VmServiceOfferingUpgradeManagerImpl manager; + + private static final long VM_ID = 42L; + private static final long OLD_OFFERING_ID = 100L; + private static final long NEW_OFFERING_ID = 200L; + private static final long POOL_ID = 11L; + private static final long TEMPLATE_ID = 7L; + private static final long ZONE_ID = 3L; + + private VirtualMachine vmForUpgrade(State state, long serviceOfferingId, long zoneId) { + VirtualMachine vm = mock(VirtualMachine.class); + lenient().when(vm.getId()).thenReturn(VM_ID); + lenient().when(vm.getState()).thenReturn(state); + lenient().when(vm.getServiceOfferingId()).thenReturn(serviceOfferingId); + lenient().when(vm.getDataCenterId()).thenReturn(zoneId); + lenient().when(vm.toString()).thenReturn("vm-" + VM_ID); + return vm; + } + + private ServiceOffering serviceOfferingForEligibility(long id, boolean dynamic, ServiceOffering.State state, boolean systemUse, long diskOfferingId) { + ServiceOffering offering = mock(ServiceOffering.class); + lenient().when(offering.getId()).thenReturn(id); + lenient().when(offering.isDynamic()).thenReturn(dynamic); + lenient().when(offering.getState()).thenReturn(state); + lenient().when(offering.isSystemUse()).thenReturn(systemUse); + lenient().when(offering.getDiskOfferingId()).thenReturn(diskOfferingId); + lenient().when(offering.getUuid()).thenReturn("offering-" + id); + lenient().when(offering.getName()).thenReturn("offering-" + id); + return offering; + } + + private DiskOfferingVO diskOfferingVo(long id, boolean useLocalStorage, String tags, String[] tagsArray) { + DiskOfferingVO offering = mock(DiskOfferingVO.class); + lenient().when(offering.getId()).thenReturn(id); + lenient().when(offering.isUseLocalStorage()).thenReturn(useLocalStorage); + lenient().when(offering.getTags()).thenReturn(tags); + lenient().when(offering.getTagsArray()).thenReturn(tagsArray); + return offering; + } + + // ---- checkIfCanUpgrade ---- + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanUpgradeRejectsNullNewOffering() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + + manager.checkIfCanUpgrade(vm, null); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanUpgradeRejectsInactiveOffering() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + ServiceOffering newOffering = serviceOfferingForEligibility(NEW_OFFERING_ID, true, ServiceOffering.State.Inactive, true, 22L); + + manager.checkIfCanUpgrade(vm, newOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanUpgradeRejectsVmStateOtherThanStoppedOrRunning() { + VirtualMachine vm = vmForUpgrade(State.Starting, OLD_OFFERING_ID, ZONE_ID); + ServiceOffering newOffering = serviceOfferingForEligibility(NEW_OFFERING_ID, true, ServiceOffering.State.Active, true, 22L); + + manager.checkIfCanUpgrade(vm, newOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanUpgradeRejectsSameStaticOffering() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + ServiceOffering newOffering = serviceOfferingForEligibility(OLD_OFFERING_ID, false, ServiceOffering.State.Active, true, 22L); + + manager.checkIfCanUpgrade(vm, newOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanUpgradeRejectsSystemUseMismatch() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + ServiceOffering newOffering = serviceOfferingForEligibility(NEW_OFFERING_ID, true, ServiceOffering.State.Active, false, 22L); + ServiceOfferingVO currentOffering = mock(ServiceOfferingVO.class); + when(currentOffering.getDiskOfferingId()).thenReturn(11L); + when(currentOffering.isSystemUse()).thenReturn(true); + DiskOfferingVO currentDiskOffering = diskOfferingVo(11L, false, "x,y", new String[] {"x", "y"}); + DiskOfferingVO newDiskOffering = diskOfferingVo(22L, false, "z,x,y", new String[] {"z", "x", "y"}); + when(serviceOfferingDao.findByIdIncludingRemoved(VM_ID, OLD_OFFERING_ID)).thenReturn(currentOffering); + when(diskOfferingDao.findByIdIncludingRemoved(11L)).thenReturn(currentDiskOffering); + when(diskOfferingDao.findById(22L)).thenReturn(newDiskOffering); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.emptyList()); + + manager.checkIfCanUpgrade(vm, newOffering); + } + + @Test + public void checkIfCanUpgradeAllowsStoppedVmWhenStorageScopeSystemUseAndTagsMatch() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + ServiceOffering newOffering = serviceOfferingForEligibility(NEW_OFFERING_ID, true, ServiceOffering.State.Active, true, 22L); + ServiceOfferingVO currentOffering = mock(ServiceOfferingVO.class); + when(currentOffering.getDiskOfferingId()).thenReturn(11L); + when(currentOffering.isSystemUse()).thenReturn(true); + DiskOfferingVO currentDiskOffering = diskOfferingVo(11L, false, "x,y", new String[] {"x", "y"}); + DiskOfferingVO newDiskOffering = diskOfferingVo(22L, false, "z,x,y", new String[] {"z", "x", "y"}); + when(serviceOfferingDao.findByIdIncludingRemoved(VM_ID, OLD_OFFERING_ID)).thenReturn(currentOffering); + when(diskOfferingDao.findByIdIncludingRemoved(11L)).thenReturn(currentDiskOffering); + when(diskOfferingDao.findById(22L)).thenReturn(newDiskOffering); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.emptyList()); + + manager.checkIfCanUpgrade(vm, newOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfCanUpgradeRejectsWhenCurrentDiskOfferingTagsAreNotSubsetOfNewTags() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + ServiceOffering newOffering = serviceOfferingForEligibility(NEW_OFFERING_ID, true, ServiceOffering.State.Active, true, 22L); + ServiceOfferingVO currentOffering = mock(ServiceOfferingVO.class); + when(currentOffering.getDiskOfferingId()).thenReturn(11L); + when(currentOffering.isSystemUse()).thenReturn(true); + DiskOfferingVO currentDiskOffering = diskOfferingVo(11L, false, "x,y", new String[] {"x", "y"}); + DiskOfferingVO newDiskOffering = diskOfferingVo(22L, false, "x", new String[] {"x"}); + when(serviceOfferingDao.findByIdIncludingRemoved(VM_ID, OLD_OFFERING_ID)).thenReturn(currentOffering); + when(diskOfferingDao.findByIdIncludingRemoved(11L)).thenReturn(currentDiskOffering); + when(diskOfferingDao.findById(22L)).thenReturn(newDiskOffering); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.emptyList()); + + manager.checkIfCanUpgrade(vm, newOffering); + } + + // ---- checkIfNewOfferingStorageScopeMatchesStoragePool ---- + + @Test + public void checkIfNewOfferingStorageScopeMatchesStoragePoolAllowsLocalToLocal() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + DiskOffering newDiskOffering = mock(DiskOffering.class); + when(newDiskOffering.isUseLocalStorage()).thenReturn(true); + VolumeVO rootVolume = mock(VolumeVO.class); + when(rootVolume.getPoolId()).thenReturn(POOL_ID); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(rootVolume)); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getScope()).thenReturn(ScopeType.HOST); + when(storagePoolDao.findById(POOL_ID)).thenReturn(pool); + + manager.checkIfNewOfferingStorageScopeMatchesStoragePool(vm, newDiskOffering); + } + + @Test + public void checkIfNewOfferingStorageScopeMatchesStoragePoolAllowsSharedToShared() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + DiskOffering newDiskOffering = mock(DiskOffering.class); + when(newDiskOffering.isUseLocalStorage()).thenReturn(false); + VolumeVO rootVolume = mock(VolumeVO.class); + when(rootVolume.getPoolId()).thenReturn(POOL_ID); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(rootVolume)); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getScope()).thenReturn(ScopeType.CLUSTER); + when(storagePoolDao.findById(POOL_ID)).thenReturn(pool); + + manager.checkIfNewOfferingStorageScopeMatchesStoragePool(vm, newDiskOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfNewOfferingStorageScopeMatchesStoragePoolRejectsLocalOfferingForSharedRoot() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + DiskOffering newDiskOffering = mock(DiskOffering.class); + when(newDiskOffering.isUseLocalStorage()).thenReturn(true); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.emptyList()); + + manager.checkIfNewOfferingStorageScopeMatchesStoragePool(vm, newDiskOffering); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkIfNewOfferingStorageScopeMatchesStoragePoolRejectsSharedOfferingForLocalRoot() { + VirtualMachine vm = vmForUpgrade(State.Stopped, OLD_OFFERING_ID, ZONE_ID); + DiskOffering newDiskOffering = mock(DiskOffering.class); + when(newDiskOffering.isUseLocalStorage()).thenReturn(false); + VolumeVO rootVolume = mock(VolumeVO.class); + when(rootVolume.getPoolId()).thenReturn(POOL_ID); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(rootVolume)); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getScope()).thenReturn(ScopeType.HOST); + when(storagePoolDao.findById(POOL_ID)).thenReturn(pool); + + manager.checkIfNewOfferingStorageScopeMatchesStoragePool(vm, newDiskOffering); + } + + // ---- isRootVolumeOnLocalStorage ---- + + @Test + public void isRootVolumeOnLocalStorageReturnsTrueForHostScope() { + VolumeVO volume = mock(VolumeVO.class); + when(volume.getPoolId()).thenReturn(POOL_ID); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(volume)); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getScope()).thenReturn(ScopeType.HOST); + when(storagePoolDao.findById(POOL_ID)).thenReturn(pool); + + assertTrue(manager.isRootVolumeOnLocalStorage(VM_ID)); + } + + @Test + public void isRootVolumeOnLocalStorageReturnsFalseForClusterScope() { + VolumeVO volume = mock(VolumeVO.class); + when(volume.getPoolId()).thenReturn(POOL_ID); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(volume)); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getScope()).thenReturn(ScopeType.CLUSTER); + when(storagePoolDao.findById(POOL_ID)).thenReturn(pool); + + assertFalse(manager.isRootVolumeOnLocalStorage(VM_ID)); + } + + @Test + public void isRootVolumeOnLocalStorageReturnsFalseForZoneScope() { + VolumeVO volume = mock(VolumeVO.class); + when(volume.getPoolId()).thenReturn(POOL_ID); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(volume)); + StoragePoolVO pool = mock(StoragePoolVO.class); + when(pool.getScope()).thenReturn(ScopeType.ZONE); + when(storagePoolDao.findById(POOL_ID)).thenReturn(pool); + + assertFalse(manager.isRootVolumeOnLocalStorage(VM_ID)); + } + + @Test + public void isRootVolumeOnLocalStorageReturnsFalseWhenNoRootVolumeExists() { + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.emptyList()); + + assertFalse(manager.isRootVolumeOnLocalStorage(VM_ID)); + verify(storagePoolDao, never()).findById(anyLong()); + } + + @Test + public void isRootVolumeOnLocalStorageReturnsFalseWhenRootVolumeHasNoPool() { + VolumeVO volume = mock(VolumeVO.class); + when(volume.getPoolId()).thenReturn(null); + when(volumeDao.findByInstanceAndType(eq(VM_ID), any())).thenReturn(Collections.singletonList(volume)); + + assertFalse(manager.isRootVolumeOnLocalStorage(VM_ID)); + verify(storagePoolDao, never()).findById(anyLong()); + } + + // ---- upgradeVmDb ---- + + private VMInstanceVO mockVmForUpgrade() { + VMInstanceVO vm = mock(VMInstanceVO.class); + when(vm.getTemplateId()).thenReturn(TEMPLATE_ID); + when(vm.getDataCenterId()).thenReturn(ZONE_ID); + when(vmInstanceDao.findById(VM_ID)).thenReturn(vm); + return vm; + } + + private ServiceOffering newOffering(long id, boolean dynamic, boolean haEnabled, boolean limitCpu) { + ServiceOffering offering = mock(ServiceOffering.class); + when(offering.getId()).thenReturn(id); + when(offering.isDynamic()).thenReturn(dynamic); + when(offering.isOfferHA()).thenReturn(haEnabled); + when(offering.getLimitCpuUse()).thenReturn(limitCpu); + return offering; + } + + @Test + public void upgradeVmDbMirrorsHaAndLimitCpuFlagsFromEntityManagerLookup() { + VMInstanceVO vm = mockVmForUpgrade(); + ServiceOffering newOff = newOffering(NEW_OFFERING_ID, false, true, true); + ServiceOffering currentOff = newOffering(OLD_OFFERING_ID, false, false, false); + ServiceOffering resolvedNew = newOffering(NEW_OFFERING_ID, false, true, true); + when(entityMgr.findById(ServiceOffering.class, NEW_OFFERING_ID)).thenReturn(resolvedNew); + when(templateDao.findByIdIncludingRemoved(TEMPLATE_ID)).thenReturn(mock(VMTemplateVO.class)); + when(userVmManager.checkIfDynamicScalingCanBeEnabled(any(), any(), any(), anyLong())).thenReturn(true); + when(vmInstanceDao.update(eq(VM_ID), any())).thenReturn(true); + + assertTrue(manager.upgradeVmDb(VM_ID, newOff, currentOff)); + + // setServiceOfferingId is invoked twice: once with the input id and again + // after the entityMgr lookup mirrors flags into the local VO. + verify(vm, times(2)).setServiceOfferingId(NEW_OFFERING_ID); + verify(vm).setHaEnabled(true); + verify(vm).setLimitCpuUse(true); + verify(vm).setDynamicallyScalable(true); + verify(vmInstanceDao).update(VM_ID, vm); + } + + @Test + public void upgradeVmDbReflectsDynamicScalingDisabled() { + VMInstanceVO vm = mockVmForUpgrade(); + ServiceOffering newOff = newOffering(NEW_OFFERING_ID, false, false, false); + ServiceOffering currentOff = newOffering(OLD_OFFERING_ID, false, false, false); + when(entityMgr.findById(ServiceOffering.class, NEW_OFFERING_ID)).thenReturn(newOff); + when(templateDao.findByIdIncludingRemoved(TEMPLATE_ID)).thenReturn(mock(VMTemplateVO.class)); + when(userVmManager.checkIfDynamicScalingCanBeEnabled(any(), any(), any(), anyLong())).thenReturn(false); + when(vmInstanceDao.update(eq(VM_ID), any())).thenReturn(true); + + manager.upgradeVmDb(VM_ID, newOff, currentOff); + + verify(vm).setDynamicallyScalable(false); + } + + @Test + public void upgradeVmDbSavesCustomDetailsWhenNewOfferingIsDynamic() { + mockVmForUpgrade(); + ServiceOffering newOff = newOffering(NEW_OFFERING_ID, true, false, false); + when(newOff.getCpu()).thenReturn(2); + when(newOff.getSpeed()).thenReturn(1000); + when(newOff.getRamSize()).thenReturn(4096); + ServiceOffering currentOff = newOffering(OLD_OFFERING_ID, false, false, false); + when(entityMgr.findById(ServiceOffering.class, NEW_OFFERING_ID)).thenReturn(newOff); + when(templateDao.findByIdIncludingRemoved(TEMPLATE_ID)).thenReturn(mock(VMTemplateVO.class)); + when(userVmManager.checkIfDynamicScalingCanBeEnabled(any(), any(), any(), anyLong())).thenReturn(true); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(new HashMap<>()); + ServiceOfferingVO unfilled = mock(ServiceOfferingVO.class); + when(unfilled.getCpu()).thenReturn(null); + when(unfilled.getSpeed()).thenReturn(null); + when(unfilled.getRamSize()).thenReturn(null); + when(serviceOfferingDao.findByIdIncludingRemoved(NEW_OFFERING_ID)).thenReturn(unfilled); + when(vmInstanceDao.update(eq(VM_ID), any())).thenReturn(true); + + manager.upgradeVmDb(VM_ID, newOff, currentOff); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + List saved = captor.getValue(); + assertEquals(3, saved.size()); + } + + @Test + public void upgradeVmDbRemovesCustomDetailsWhenDowngradingFromDynamicToStatic() { + mockVmForUpgrade(); + ServiceOffering newOff = newOffering(NEW_OFFERING_ID, false, false, false); + ServiceOffering currentOff = newOffering(OLD_OFFERING_ID, true, false, false); + when(entityMgr.findById(ServiceOffering.class, NEW_OFFERING_ID)).thenReturn(newOff); + when(templateDao.findByIdIncludingRemoved(TEMPLATE_ID)).thenReturn(mock(VMTemplateVO.class)); + when(userVmManager.checkIfDynamicScalingCanBeEnabled(any(), any(), any(), anyLong())).thenReturn(false); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(new HashMap<>(Map.of("other", "v"))); + when(vmInstanceDao.update(eq(VM_ID), any())).thenReturn(true); + + manager.upgradeVmDb(VM_ID, newOff, currentOff); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + List saved = captor.getValue(); + assertEquals(1, saved.size()); + assertEquals("other", saved.get(0).getName()); + } + + @Test + public void upgradeVmDbSkipsCustomDetailsHandlingWhenBothOfferingsStatic() { + mockVmForUpgrade(); + ServiceOffering newOff = newOffering(NEW_OFFERING_ID, false, false, false); + ServiceOffering currentOff = newOffering(OLD_OFFERING_ID, false, false, false); + when(entityMgr.findById(ServiceOffering.class, NEW_OFFERING_ID)).thenReturn(newOff); + when(templateDao.findByIdIncludingRemoved(TEMPLATE_ID)).thenReturn(mock(VMTemplateVO.class)); + when(userVmManager.checkIfDynamicScalingCanBeEnabled(any(), any(), any(), anyLong())).thenReturn(false); + when(vmInstanceDao.update(eq(VM_ID), any())).thenReturn(true); + + manager.upgradeVmDb(VM_ID, newOff, currentOff); + + verify(vmInstanceDetailsDao, never()).listDetailsKeyPairs(VM_ID); + verify(vmInstanceDetailsDao, never()).saveDetails(any()); + } + + @Test + public void upgradeVmDbPropagatesVmInstanceDaoUpdateResult() { + mockVmForUpgrade(); + ServiceOffering newOff = newOffering(NEW_OFFERING_ID, false, false, false); + ServiceOffering currentOff = newOffering(OLD_OFFERING_ID, false, false, false); + when(entityMgr.findById(ServiceOffering.class, NEW_OFFERING_ID)).thenReturn(newOff); + when(templateDao.findByIdIncludingRemoved(TEMPLATE_ID)).thenReturn(mock(VMTemplateVO.class)); + when(userVmManager.checkIfDynamicScalingCanBeEnabled(any(), any(), any(), anyLong())).thenReturn(false); + when(vmInstanceDao.update(eq(VM_ID), any())).thenReturn(false); + + assertFalse(manager.upgradeVmDb(VM_ID, newOff, currentOff)); + } + + // ---- removeCustomOfferingDetails ---- + + @Test + public void removeCustomOfferingDetailsStripsDynamicTrioAndPreservesOthers() { + Map details = new HashMap<>(); + details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), "4"); + details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), "2000"); + details.put(UsageEventVO.DynamicParameters.memory.name(), "8192"); + details.put("keepMe", "yes"); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(details); + + manager.removeCustomOfferingDetails(VM_ID); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + List saved = captor.getValue(); + assertEquals(1, saved.size()); + assertEquals("keepMe", saved.get(0).getName()); + assertEquals("yes", saved.get(0).getValue()); + } + + @Test + public void removeCustomOfferingDetailsWritesEmptyListWhenOnlyDynamicTrioPresent() { + Map details = new HashMap<>(); + details.put(UsageEventVO.DynamicParameters.cpuNumber.name(), "4"); + details.put(UsageEventVO.DynamicParameters.cpuSpeed.name(), "2000"); + details.put(UsageEventVO.DynamicParameters.memory.name(), "8192"); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(details); + + manager.removeCustomOfferingDetails(VM_ID); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + assertTrue(captor.getValue().isEmpty()); + } + + @Test + public void removeCustomOfferingDetailsHandlesEmptyMap() { + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(new HashMap<>()); + + manager.removeCustomOfferingDetails(VM_ID); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + assertTrue(captor.getValue().isEmpty()); + } + + // ---- saveCustomOfferingDetails ---- + + @Test + public void saveCustomOfferingDetailsPersistsAllCustomFieldsWhenOfferingHasNoneFilled() { + ServiceOffering offering = mock(ServiceOffering.class); + when(offering.getId()).thenReturn(NEW_OFFERING_ID); + when(offering.getCpu()).thenReturn(2); + when(offering.getSpeed()).thenReturn(1500); + when(offering.getRamSize()).thenReturn(4096); + + ServiceOfferingVO unfilled = mock(ServiceOfferingVO.class); + when(unfilled.getCpu()).thenReturn(null); + when(unfilled.getSpeed()).thenReturn(null); + when(unfilled.getRamSize()).thenReturn(null); + when(serviceOfferingDao.findByIdIncludingRemoved(NEW_OFFERING_ID)).thenReturn(unfilled); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(new HashMap<>()); + + manager.saveCustomOfferingDetails(VM_ID, offering); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + Map saved = toMap(captor.getValue()); + assertEquals("2", saved.get(UsageEventVO.DynamicParameters.cpuNumber.name())); + assertEquals("1500", saved.get(UsageEventVO.DynamicParameters.cpuSpeed.name())); + assertEquals("4096", saved.get(UsageEventVO.DynamicParameters.memory.name())); + } + + @Test + public void saveCustomOfferingDetailsSkipsFieldsFilledOnUnderlyingOffering() { + ServiceOffering offering = mock(ServiceOffering.class); + when(offering.getId()).thenReturn(NEW_OFFERING_ID); + when(offering.getCpu()).thenReturn(2); + when(offering.getRamSize()).thenReturn(4096); + + ServiceOfferingVO unfilled = mock(ServiceOfferingVO.class); + when(unfilled.getCpu()).thenReturn(null); + when(unfilled.getSpeed()).thenReturn(1000); + when(unfilled.getRamSize()).thenReturn(null); + when(serviceOfferingDao.findByIdIncludingRemoved(NEW_OFFERING_ID)).thenReturn(unfilled); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(new HashMap<>()); + + manager.saveCustomOfferingDetails(VM_ID, offering); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + Map saved = toMap(captor.getValue()); + assertEquals("2", saved.get(UsageEventVO.DynamicParameters.cpuNumber.name())); + assertNull(saved.get(UsageEventVO.DynamicParameters.cpuSpeed.name())); + assertEquals("4096", saved.get(UsageEventVO.DynamicParameters.memory.name())); + } + + @Test + public void saveCustomOfferingDetailsPreservesPreexistingNonCustomDetails() { + ServiceOffering offering = mock(ServiceOffering.class); + when(offering.getId()).thenReturn(NEW_OFFERING_ID); + when(offering.getCpu()).thenReturn(4); + when(offering.getSpeed()).thenReturn(2000); + when(offering.getRamSize()).thenReturn(8192); + + ServiceOfferingVO unfilled = mock(ServiceOfferingVO.class); + when(unfilled.getCpu()).thenReturn(null); + when(unfilled.getSpeed()).thenReturn(null); + when(unfilled.getRamSize()).thenReturn(null); + when(serviceOfferingDao.findByIdIncludingRemoved(NEW_OFFERING_ID)).thenReturn(unfilled); + Map existing = new HashMap<>(); + existing.put("custom-tag", "foo"); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(existing); + + manager.saveCustomOfferingDetails(VM_ID, offering); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + Map saved = toMap(captor.getValue()); + assertEquals("foo", saved.get("custom-tag")); + assertEquals("4", saved.get(UsageEventVO.DynamicParameters.cpuNumber.name())); + } + + @Test + public void saveCustomOfferingDetailsWritesNoDynamicTrioWhenAllFieldsFilledOnUnderlyingOffering() { + ServiceOffering offering = mock(ServiceOffering.class); + when(offering.getId()).thenReturn(NEW_OFFERING_ID); + + ServiceOfferingVO unfilled = mock(ServiceOfferingVO.class); + when(unfilled.getCpu()).thenReturn(4); + when(unfilled.getSpeed()).thenReturn(2000); + when(unfilled.getRamSize()).thenReturn(4096); + when(serviceOfferingDao.findByIdIncludingRemoved(NEW_OFFERING_ID)).thenReturn(unfilled); + when(vmInstanceDetailsDao.listDetailsKeyPairs(VM_ID)).thenReturn(new HashMap<>()); + + manager.saveCustomOfferingDetails(VM_ID, offering); + + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + verify(vmInstanceDetailsDao).saveDetails(captor.capture()); + assertTrue(captor.getValue().isEmpty()); + } + + private static Map toMap(List details) { + Map out = new HashMap<>(); + for (VMInstanceDetailVO d : details) { + out.put(d.getName(), d.getValue()); + } + return out; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmStartProfilePreparationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmStartProfilePreparationServiceImplTest.java new file mode 100644 index 000000000000..1f8f21185bee --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmStartProfilePreparationServiceImplTest.java @@ -0,0 +1,212 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterDetailsVO; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmStartProfilePreparationServiceImplTest { + + private static final long VM_ID = 42L; + private static final long CLUSTER_ID = 7L; + private static final long ZONE_ID = 11L; + private static final long POD_ID = 13L; + + @InjectMocks + private VmStartProfilePreparationServiceImpl service; + + @Mock + private ClusterDetailsDao clusterDetailsDao; + @Mock + private VMInstanceDetailsDao vmInstanceDetailsDao; + @Mock + private VolumeDao volumeDao; + @Mock + private NicDao nicsDao; + @Mock + private VirtualMachineProfile vmProfile; + + @Test + public void updateOverCommitRatioForVmProfileSetsRatiosAndDoesNotAddDetailsWhenClusterRatiosAreOneAndVmDetailsAbsent() { + stubVmProfile(); + stubClusterRatios("1.0", "1.0"); + when(vmInstanceDetailsDao.findDetail(VM_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(null); + when(vmInstanceDetailsDao.findDetail(VM_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(null); + + service.updateOverCommitRatioForVmProfile(vmProfile, CLUSTER_ID); + + verify(vmProfile).setCpuOvercommitRatio(1.0f); + verify(vmProfile).setMemoryOvercommitRatio(1.0f); + verify(vmInstanceDetailsDao, never()).addDetail(anyLong(), anyString(), anyString(), anyBoolean()); + } + + @Test + public void updateOverCommitRatioForVmProfileAddsDetailsWhenClusterRatiosAreGreaterThanOneAndVmDetailsAbsent() { + stubVmProfile(); + stubClusterRatios("2.0", "1.5"); + when(vmInstanceDetailsDao.findDetail(VM_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(null); + when(vmInstanceDetailsDao.findDetail(VM_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(null); + + service.updateOverCommitRatioForVmProfile(vmProfile, CLUSTER_ID); + + verify(vmInstanceDetailsDao).addDetail(VM_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO, "2.0", true); + verify(vmInstanceDetailsDao).addDetail(VM_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, "1.5", true); + verify(vmProfile).setCpuOvercommitRatio(2.0f); + verify(vmProfile).setMemoryOvercommitRatio(1.5f); + } + + @Test + public void updateOverCommitRatioForVmProfileReplacesDetailsWhenVmDetailValuesDifferFromClusterDetails() { + stubVmProfile(); + stubClusterRatios("2.0", "1.5"); + when(vmInstanceDetailsDao.findDetail(VM_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO)) + .thenReturn(new VMInstanceDetailVO(VM_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO, "1.0", true)); + when(vmInstanceDetailsDao.findDetail(VM_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)) + .thenReturn(new VMInstanceDetailVO(VM_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, "1.0", true)); + + service.updateOverCommitRatioForVmProfile(vmProfile, CLUSTER_ID); + + verify(vmInstanceDetailsDao).addDetail(VM_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO, "2.0", true); + verify(vmInstanceDetailsDao).addDetail(VM_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, "1.5", true); + } + + @Test + public void areAllVolumesAllocatedReturnsTrueForNullOrEmptyVolumeList() { + when(volumeDao.findByInstance(VM_ID)).thenReturn(null, List.of()); + + assertTrue(service.areAllVolumesAllocated(VM_ID)); + assertTrue(service.areAllVolumesAllocated(VM_ID)); + } + + @Test + public void areAllVolumesAllocatedReturnsTrueWhenAllVmVolumesAreAllocated() { + when(volumeDao.findByInstance(VM_ID)).thenReturn(List.of(volume(Volume.State.Allocated), volume(Volume.State.Allocated))); + + assertTrue(service.areAllVolumesAllocated(VM_ID)); + } + + @Test + public void areAllVolumesAllocatedReturnsFalseWhenAnyVmVolumeIsNotAllocated() { + when(volumeDao.findByInstance(VM_ID)).thenReturn(List.of(volume(Volume.State.Allocated), volume(Volume.State.Ready))); + + assertFalse(service.areAllVolumesAllocated(VM_ID)); + } + + @Test + public void conditionallySetPodToDeployInNullsPodWhenAllVolumesAreAllocated() { + VMInstanceVO vm = vmWithPod(); + when(volumeDao.findByInstance(VM_ID)).thenReturn(List.of(volume(Volume.State.Allocated))); + + service.conditionallySetPodToDeployIn(vm); + + assertNull(vm.getPodIdToDeployIn()); + } + + @Test + public void conditionallySetPodToDeployInKeepsPodWhenMigrationAcrossClustersIsFalseAndVolumeIsNotAllocated() { + VMInstanceVO vm = vmWithPod(); + when(volumeDao.findByInstance(VM_ID)).thenReturn(List.of(volume(Volume.State.Ready))); + + service.conditionallySetPodToDeployIn(vm); + + assertTrue(POD_ID == vm.getPodIdToDeployIn()); + } + + @Test + public void resetVmNicsDeviceIdSortsByExistingDeviceIdAndUpdatesOnlyChangedNics() { + NicVO first = nic(100L, 0); + NicVO second = nic(200L, 2); + NicVO third = nic(300L, 5); + when(nicsDao.listByVmId(VM_ID)).thenReturn(new ArrayList<>(List.of(third, first, second))); + + service.resetVmNicsDeviceId(VM_ID); + + verify(nicsDao, never()).update(100L, first); + verify(nicsDao).update(200L, second); + verify(nicsDao).update(300L, third); + assertTrue(first.getDeviceId() == 0); + assertTrue(second.getDeviceId() == 1); + assertTrue(third.getDeviceId() == 2); + } + + @Test + public void logBootModeParametersWithNullParamsIsNoOp() { + service.logBootModeParameters(null); + + verifyNoInteractions(clusterDetailsDao, vmInstanceDetailsDao, volumeDao, nicsDao); + } + + private void stubVmProfile() { + when(vmProfile.getId()).thenReturn(VM_ID); + } + + private void stubClusterRatios(String cpuRatio, String memoryRatio) { + when(clusterDetailsDao.findDetail(CLUSTER_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO)) + .thenReturn(new ClusterDetailsVO(CLUSTER_ID, VmDetailConstants.CPU_OVER_COMMIT_RATIO, cpuRatio)); + when(clusterDetailsDao.findDetail(CLUSTER_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)) + .thenReturn(new ClusterDetailsVO(CLUSTER_ID, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, memoryRatio)); + } + + private VolumeVO volume(Volume.State state) { + VolumeVO volume = new VolumeVO("volume", ZONE_ID, POD_ID, 1L, 1L, VM_ID, "folder", "path", null, 1L, Volume.Type.ROOT); + volume.setState(state); + return volume; + } + + private VMInstanceVO vmWithPod() { + VMInstanceVO vm = new VMInstanceVO(); + ReflectionTestUtils.setField(vm, "id", VM_ID); + ReflectionTestUtils.setField(vm, "dataCenterId", ZONE_ID); + vm.setPodIdToDeployIn(POD_ID); + return vm; + } + + private NicVO nic(long id, int deviceId) { + NicVO nic = new NicVO("reserver", VM_ID, 1L, VirtualMachine.Type.User); + nic.id = id; + nic.setDeviceId(deviceId); + return nic; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmStatsCollectorImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmStatsCollectorImplTest.java new file mode 100644 index 000000000000..d232f70a7838 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmStatsCollectorImplTest.java @@ -0,0 +1,454 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.GetVmDiskStatsAnswer; +import com.cloud.agent.api.GetVmDiskStatsCommand; +import com.cloud.agent.api.GetVmNetworkStatsAnswer; +import com.cloud.agent.api.GetVmNetworkStatsCommand; +import com.cloud.agent.api.GetVmStatsAnswer; +import com.cloud.agent.api.GetVmStatsCommand; +import com.cloud.agent.api.VmDiskStatsEntry; +import com.cloud.agent.api.VmNetworkStatsEntry; +import com.cloud.agent.api.VmStatsEntry; +import com.cloud.host.Host; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmStatsCollectorImplTest { + + @Mock private AgentManager agentMgr; + @Mock private VMInstanceDao vmInstanceDao; + + @InjectMocks + private VmStatsCollectorImpl collector; + + private static final long HOST_ID = 7L; + private static final long VM_ID_A = 11L; + private static final long VM_ID_B = 22L; + + private Host host; + + @Before + public void setup() { + host = mock(Host.class); + when(host.getId()).thenReturn(HOST_ID); + when(host.getGuid()).thenReturn("guid-host-7"); + when(host.getName()).thenReturn("host-7"); + } + + private Map nameIdMap() { + Map map = new LinkedHashMap<>(); + map.put("i-a", VM_ID_A); + map.put("i-b", VM_ID_B); + return map; + } + + // ---- getVirtualMachineStatistics(Host, List) ---- + + @Test + public void vmStatsByIdsReturnsEmptyForNullList() { + HashMap result = collector.getVirtualMachineStatistics(host, (List) null); + + assertTrue(result.isEmpty()); + verify(vmInstanceDao, never()).getNameIdMapForVmIds(anyList()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void vmStatsByIdsReturnsEmptyForEmptyList() { + HashMap result = collector.getVirtualMachineStatistics(host, Collections.emptyList()); + + assertTrue(result.isEmpty()); + verify(vmInstanceDao, never()).getNameIdMapForVmIds(anyList()); + } + + @Test + public void vmStatsByIdsResolvesNamesAndDelegatesToMapOverload() { + List ids = Arrays.asList(VM_ID_A, VM_ID_B); + Map resolved = nameIdMap(); + when(vmInstanceDao.getNameIdMapForVmIds(ids)).thenReturn(resolved); + VmStatsEntry entryA = new VmStatsEntry(); + HashMap answerMap = new HashMap<>(); + answerMap.put("i-a", entryA); + GetVmStatsAnswer answer = mock(GetVmStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(answer); + + HashMap result = collector.getVirtualMachineStatistics(host, ids); + + assertEquals(1, result.size()); + assertSame(entryA, result.get(VM_ID_A)); + verify(vmInstanceDao).getNameIdMapForVmIds(ids); + } + + // ---- getVirtualMachineStatistics(Host, Map) ---- + + @Test + public void vmStatsByMapReturnsEmptyForNullMap() { + HashMap result = collector.getVirtualMachineStatistics(host, (Map) null); + + assertTrue(result.isEmpty()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void vmStatsByMapReturnsEmptyForEmptyMap() { + HashMap result = collector.getVirtualMachineStatistics(host, Collections.emptyMap()); + + assertTrue(result.isEmpty()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void vmStatsByMapReturnsEmptyWhenAgentReturnsNullAnswer() { + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(null); + + HashMap result = collector.getVirtualMachineStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void vmStatsByMapReturnsEmptyWhenAnswerReportsFailure() { + GetVmStatsAnswer answer = mock(GetVmStatsAnswer.class); + when(answer.getResult()).thenReturn(false); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(answer); + + HashMap result = collector.getVirtualMachineStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void vmStatsByMapReturnsEmptyWhenAnswerHasNullStatsMap() { + GetVmStatsAnswer answer = mock(GetVmStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmStatsMap()).thenReturn(null); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(answer); + + HashMap result = collector.getVirtualMachineStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void vmStatsByMapRekeysEachEntryByVmId() { + VmStatsEntry entryA = new VmStatsEntry(); + VmStatsEntry entryB = new VmStatsEntry(); + HashMap answerMap = new HashMap<>(); + answerMap.put("i-a", entryA); + answerMap.put("i-b", entryB); + GetVmStatsAnswer answer = mock(GetVmStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(answer); + + HashMap result = collector.getVirtualMachineStatistics(host, nameIdMap()); + + assertEquals(2, result.size()); + assertSame(entryA, result.get(VM_ID_A)); + assertSame(entryB, result.get(VM_ID_B)); + } + + @Test + public void vmStatsByMapSkipsEntriesNotInInputMap() { + VmStatsEntry entry = new VmStatsEntry(); + HashMap answerMap = new HashMap<>(); + answerMap.put("i-ghost", entry); // not in name->id map + GetVmStatsAnswer answer = mock(GetVmStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(answer); + + HashMap result = collector.getVirtualMachineStatistics(host, nameIdMap()); + + // ghost rekeys to a null vmId, but the entry itself is preserved + assertEquals(1, result.size()); + assertTrue(result.containsKey(null)); + } + + @Test + public void vmStatsByMapSendsCommandWithInstanceNamesGuidAndHostName() { + GetVmStatsAnswer answer = mock(GetVmStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmStatsMap()).thenReturn(new HashMap<>()); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmStatsCommand.class))).thenReturn(answer); + + collector.getVirtualMachineStatistics(host, nameIdMap()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(GetVmStatsCommand.class); + verify(agentMgr).easySend(eq(HOST_ID), captor.capture()); + GetVmStatsCommand sent = captor.getValue(); + assertTrue(sent.getVmNames().containsAll(Arrays.asList("i-a", "i-b"))); + assertEquals("guid-host-7", sent.getHostGuid()); + assertEquals("host-7", sent.getHostName()); + } + + // ---- getVmDiskStatistics ---- + + @Test + public void diskStatsReturnsEmptyForNullMap() { + HashMap> result = collector.getVmDiskStatistics(host, null); + + assertTrue(result.isEmpty()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void diskStatsReturnsEmptyForEmptyMap() { + HashMap> result = collector.getVmDiskStatistics(host, Collections.emptyMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void diskStatsReturnsEmptyOnNullAnswer() { + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(null); + + HashMap> result = collector.getVmDiskStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void diskStatsReturnsEmptyOnFailingAnswer() { + Answer answer = mock(GetVmDiskStatsAnswer.class); + when(answer.getResult()).thenReturn(false); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmDiskStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void diskStatsReturnsEmptyWhenAnswerHasNullMap() { + GetVmDiskStatsAnswer answer = mock(GetVmDiskStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmDiskStatsMap()).thenReturn(null); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmDiskStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void diskStatsRekeysEachListByVmId() { + List listA = Arrays.asList(mock(VmDiskStatsEntry.class), mock(VmDiskStatsEntry.class)); + List listB = Collections.singletonList(mock(VmDiskStatsEntry.class)); + HashMap> answerMap = new HashMap<>(); + answerMap.put("i-a", listA); + answerMap.put("i-b", listB); + GetVmDiskStatsAnswer answer = mock(GetVmDiskStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmDiskStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmDiskStatistics(host, nameIdMap()); + + assertEquals(2, result.size()); + assertSame(listA, result.get(VM_ID_A)); + assertSame(listB, result.get(VM_ID_B)); + } + + // ---- getVmNetworkStatistics ---- + + @Test + public void networkStatsReturnsEmptyForNullMap() { + HashMap> result = collector.getVmNetworkStatistics(host, null); + + assertTrue(result.isEmpty()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void networkStatsReturnsEmptyForEmptyMap() { + HashMap> result = collector.getVmNetworkStatistics(host, Collections.emptyMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void networkStatsReturnsEmptyOnNullAnswer() { + when(agentMgr.easySend(eq(HOST_ID), any(GetVmNetworkStatsCommand.class))).thenReturn(null); + + HashMap> result = collector.getVmNetworkStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void networkStatsReturnsEmptyOnFailingAnswer() { + Answer answer = mock(GetVmNetworkStatsAnswer.class); + when(answer.getResult()).thenReturn(false); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmNetworkStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmNetworkStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void networkStatsReturnsEmptyWhenAnswerHasNullMap() { + GetVmNetworkStatsAnswer answer = mock(GetVmNetworkStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmNetworkStatsMap()).thenReturn(null); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmNetworkStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmNetworkStatistics(host, nameIdMap()); + + assertTrue(result.isEmpty()); + } + + @Test + public void networkStatsRekeysEachListByVmId() { + List listA = Arrays.asList(mock(VmNetworkStatsEntry.class)); + List listB = Arrays.asList(mock(VmNetworkStatsEntry.class), mock(VmNetworkStatsEntry.class)); + HashMap> answerMap = new HashMap<>(); + answerMap.put("i-a", listA); + answerMap.put("i-b", listB); + GetVmNetworkStatsAnswer answer = mock(GetVmNetworkStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmNetworkStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmNetworkStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmNetworkStatistics(host, nameIdMap()); + + assertEquals(2, result.size()); + assertSame(listA, result.get(VM_ID_A)); + assertSame(listB, result.get(VM_ID_B)); + } + + @Test + public void networkStatsSendsCommandWithInstanceNamesGuidAndHostName() { + GetVmNetworkStatsAnswer answer = mock(GetVmNetworkStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmNetworkStatsMap()).thenReturn(new HashMap<>()); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmNetworkStatsCommand.class))).thenReturn(answer); + + collector.getVmNetworkStatistics(host, nameIdMap()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(GetVmNetworkStatsCommand.class); + verify(agentMgr).easySend(eq(HOST_ID), captor.capture()); + GetVmNetworkStatsCommand sent = captor.getValue(); + assertTrue(sent.getVmNames().containsAll(Arrays.asList("i-a", "i-b"))); + assertEquals("guid-host-7", sent.getHostGuid()); + assertEquals("host-7", sent.getHostName()); + } + + @Test + public void unknownVmNameInDiskAnswerMapsToNullKey() { + List orphan = Collections.singletonList(mock(VmDiskStatsEntry.class)); + HashMap> answerMap = new HashMap<>(); + answerMap.put("i-ghost", orphan); + GetVmDiskStatsAnswer answer = mock(GetVmDiskStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmDiskStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmDiskStatistics(host, nameIdMap()); + + assertEquals(1, result.size()); + assertSame(orphan, result.get(null)); + } + + @Test + public void diskStatsCommandIncludesGuidAndHostName() { + GetVmDiskStatsAnswer answer = mock(GetVmDiskStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmDiskStatsMap()).thenReturn(new HashMap<>()); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(answer); + + collector.getVmDiskStatistics(host, nameIdMap()); + + ArgumentCaptor captor = ArgumentCaptor.forClass(GetVmDiskStatsCommand.class); + verify(agentMgr).easySend(eq(HOST_ID), captor.capture()); + GetVmDiskStatsCommand sent = captor.getValue(); + assertEquals("guid-host-7", sent.getHostGuid()); + assertEquals("host-7", sent.getHostName()); + assertTrue(sent.getVmNames().containsAll(Arrays.asList("i-a", "i-b"))); + } + + @Test + public void vmStatsByIdsHandlesNullDaoResultGracefully() { + List ids = Arrays.asList(VM_ID_A); + when(vmInstanceDao.getNameIdMapForVmIds(ids)).thenReturn(null); + + HashMap result = collector.getVirtualMachineStatistics(host, ids); + + assertTrue(result.isEmpty()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void vmStatsByIdsHandlesEmptyDaoResultGracefully() { + List ids = Arrays.asList(VM_ID_A); + when(vmInstanceDao.getNameIdMapForVmIds(ids)).thenReturn(Collections.emptyMap()); + + HashMap result = collector.getVirtualMachineStatistics(host, ids); + + assertTrue(result.isEmpty()); + verify(agentMgr, never()).easySend(eq(HOST_ID), any()); + } + + @Test + public void diskStatsLeavesNullKeyResultsWhenAnswerEntriesAreNullValued() { + HashMap> answerMap = new HashMap<>(); + answerMap.put("i-a", null); + GetVmDiskStatsAnswer answer = mock(GetVmDiskStatsAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVmDiskStatsMap()).thenReturn(answerMap); + when(agentMgr.easySend(eq(HOST_ID), any(GetVmDiskStatsCommand.class))).thenReturn(answer); + + HashMap> result = collector.getVmDiskStatistics(host, nameIdMap()); + + assertEquals(1, result.size()); + assertNull(result.get(VM_ID_A)); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmStopCommandServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmStopCommandServiceImplTest.java new file mode 100644 index 000000000000..b8d0335cd98e --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmStopCommandServiceImplTest.java @@ -0,0 +1,189 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.StopCommand; +import com.cloud.agent.api.to.DpdkTO; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmStopCommandServiceImplTest { + + private static final long VM_ID = 42L; + + @Mock + private NicDao nicsDao; + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private VmVlanPersistenceMappingService vmVlanPersistenceMappingService; + + @InjectMocks + private VmStopCommandServiceImpl service; + + @Test + public void decorateStopCommandUsesControlNicForConsoleProxy() { + VirtualMachine vm = mockVm(VirtualMachine.Type.ConsoleProxy); + NicVO nic = new NicVO("reserver", VM_ID, 1L, VirtualMachine.Type.ConsoleProxy); + nic.setIPv4Address("10.1.1.10"); + when(nicsDao.getControlNicForVM(VM_ID)).thenReturn(nic); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertEquals("10.1.1.10", command.getControlIp()); + } + + @Test + public void decorateStopCommandUsesControlNicForSecondaryStorageVm() { + VMInstanceVO vm = mockVm(VirtualMachine.Type.SecondaryStorageVm); + NicVO nic = new NicVO("reserver", VM_ID, 1L, VirtualMachine.Type.SecondaryStorageVm); + nic.setIPv4Address("10.1.1.20"); + when(nicsDao.getControlNicForVM(VM_ID)).thenReturn(nic); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertEquals("10.1.1.20", command.getControlIp()); + } + + @Test + public void decorateStopCommandUsesPrivateIpForDomainRouter() { + VirtualMachine vm = mockVm(VirtualMachine.Type.DomainRouter); + when(vm.getPrivateIpAddress()).thenReturn("172.16.0.1"); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertEquals("172.16.0.1", command.getControlIp()); + verify(nicsDao, never()).getControlNicForVM(VM_ID); + } + + @Test + public void decorateStopCommandLeavesControlIpNullForUserVm() { + VirtualMachine vm = mockVm(VirtualMachine.Type.User); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertNull(command.getControlIp()); + verify(nicsDao, never()).getControlNicForVM(VM_ID); + } + + @Test + public void decorateStopCommandLeavesControlIpNullForNullVmType() { + VirtualMachine vm = mockVm(null); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertNull(command.getControlIp()); + verify(nicsDao, never()).getControlNicForVM(VM_ID); + } + + @Test + public void decorateStopCommandAppliesNonEmptyVlanPersistenceMap() { + VirtualMachine vm = mockVm(VirtualMachine.Type.User); + Map vlanMap = new HashMap<>(); + vlanMap.put("vlan://100", true); + when(vmVlanPersistenceMappingService.getVlanToPersistenceMapForVM(VM_ID)).thenReturn(vlanMap); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertSame(vlanMap, command.getVlanToPersistenceMap()); + } + + @Test + public void decorateStopCommandSkipsEmptyVlanPersistenceMap() { + VirtualMachine vm = mockVm(VirtualMachine.Type.User); + when(vmVlanPersistenceMappingService.getVlanToPersistenceMapForVM(VM_ID)).thenReturn(Collections.emptyMap()); + + StopCommand command = new StopCommand(vm, true, false); + + service.decorateStopCommandWithNetworkDetails(command, vm); + + assertNull(command.getVlanToPersistenceMap()); + } + + @Test + public void buildCleanupCommandCopiesDpdkMapping() { + VirtualMachine vm = mockVm(VirtualMachine.Type.User); + Map dpdkInterfaceMapping = Map.of("eth0", new DpdkTO("/ovs", "vhost0", "client")); + + StopCommand command = service.buildCleanupCommand(vm, true, dpdkInterfaceMapping); + + assertEquals(vm.getInstanceName(), command.getVmName()); + assertTrue(command.executeInSequence()); + assertFalse(command.checkBeforeCleanup()); + assertSame(dpdkInterfaceMapping, command.getDpdkInterfaceMapping()); + } + + @Test + public void buildCleanupCommandForStringResolvesVmAndDecoratesNetworkDetails() { + String instanceName = "s-1-VM"; + VMInstanceVO vm = mockVm(VirtualMachine.Type.SecondaryStorageVm); + NicVO nic = new NicVO("reserver", VM_ID, 1L, VirtualMachine.Type.SecondaryStorageVm); + nic.setIPv4Address("10.1.1.30"); + when(vmInstanceDao.findVMByInstanceName(instanceName)).thenReturn(vm); + when(nicsDao.getControlNicForVM(VM_ID)).thenReturn(nic); + + StopCommand command = service.buildCleanupCommand(instanceName, false); + + assertEquals(instanceName, command.getVmName()); + assertFalse(command.executeInSequence()); + assertFalse(command.checkBeforeCleanup()); + assertEquals("10.1.1.30", command.getControlIp()); + } + + private VMInstanceVO mockVm(VirtualMachine.Type type) { + VMInstanceVO vm = mock(VMInstanceVO.class); + lenient().when(vm.getId()).thenReturn(VM_ID); + lenient().when(vm.getType()).thenReturn(type); + lenient().when(vm.getInstanceName()).thenReturn("i-2-VM"); + return vm; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmStopOrchestrationServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmStopOrchestrationServiceImplTest.java new file mode 100644 index 000000000000..e26d299e35af --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmStopOrchestrationServiceImplTest.java @@ -0,0 +1,212 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmStopOrchestrationServiceImplTest { + + private static final String VM_UUID = "vm-uuid"; + private static final long VM_ID = 42L; + + @InjectMocks + private VmStopOrchestrationServiceImpl service; + + @Mock + private VMInstanceDao vmDao; + @Mock + private VmWorkJobQueueService vmWorkJobQueueService; + @Mock + private NetworkOrchestrationService networkMgr; + @Mock + private VolumeOrchestrationService volumeMgr; + @Mock + private VMInstanceVO vm; + @Mock + private VirtualMachineManagerImpl virtualMachineManager; + + @Test + public void advanceStopDispatchesThroughJobQueueWhenNotAlreadyInWorkJob() throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + Outcome outcome = mock(Outcome.class); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(false); + when(vmWorkJobQueueService.stopVmThroughJobQueue(VM_UUID, true)).thenReturn(outcome); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + + service.advanceStop(VM_UUID, true); + } + + verify(vmWorkJobQueueService).retrieveVmFromJobOutcome(outcome, VM_UUID, "stopVm"); + verify(vmWorkJobQueueService).retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + } + + @Test + public void releaseVmResourcesSkipsStorageReleaseForBareMetal() throws Exception { + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + when(vm.getHypervisorType()).thenReturn(HypervisorType.BareMetal); + + service.releaseVmResources(profile, true); + + verify(networkMgr).release(profile, true); + verifyNoInteractions(volumeMgr); + } + + @Test + public void cleanupUsesManagerSendStopHookForRunningVm() throws Exception { + VirtualMachineGuru guru = mock(VirtualMachineGuru.class); + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Running); + when(virtualMachineManager.sendStop(guru, profile, true, false)).thenReturn(true); + + boolean result = service.cleanup(guru, profile, null, null, true); + + assertTrue(result); + verify(virtualMachineManager).sendStop(guru, profile, true, false); + verify(networkMgr).release(profile, true); + } + + @Test + public void releaseVmResourcesReleasesNetworkAndStorageForKvm() throws Exception { + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + + service.releaseVmResources(profile, false); + + verify(networkMgr).release(profile, false); + verify(volumeMgr).release(profile); + } + + @Test + public void releaseVmResourcesSkipsStorageForExternalHypervisor() throws Exception { + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + when(vm.getHypervisorType()).thenReturn(HypervisorType.External); + + service.releaseVmResources(profile, false); + + verify(networkMgr).release(profile, false); + verifyNoInteractions(volumeMgr); + } + + @Test + public void cleanupForStoppedVmReleasesResourcesWithoutSendingStop() throws Exception { + VirtualMachineGuru guru = mock(VirtualMachineGuru.class); + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + + boolean result = service.cleanup(guru, profile, null, null, false); + + assertTrue(result); + verify(networkMgr).release(profile, false); + verify(virtualMachineManager, never()).sendStop(guru, profile, false, false); + } + + @Test + public void cleanupReturnsFalseWhenSendStopFailsForRunningVm() throws Exception { + VirtualMachineGuru guru = mock(VirtualMachineGuru.class); + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Running); + when(virtualMachineManager.sendStop(guru, profile, false, false)).thenReturn(false); + + boolean result = service.cleanup(guru, profile, null, null, false); + + assertFalse(result); + } + + @Test + public void advanceStopCreateAndExpungesPlaceholderWhenInWorkJob() throws Exception { + AsyncJobExecutionContext jobContext = mock(AsyncJobExecutionContext.class); + when(jobContext.isJobDispatchedBy(VmWorkConstants.VM_WORK_JOB_DISPATCHER)).thenReturn(true); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO placeholder = mock(org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO.class); + when(vmWorkJobQueueService.createPlaceHolderWork(VM_ID)).thenReturn(placeholder); + // orchestrateStop calls advanceStop which requires more mocking — just check placeholder lifecycle + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + + try (MockedStatic context = mockStatic(AsyncJobExecutionContext.class)) { + context.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobContext); + service.advanceStop(VM_UUID, false); + } + + verify(vmWorkJobQueueService).createPlaceHolderWork(VM_ID); + verify(vmWorkJobQueueService).expungePlaceHolderWork(placeholder); + } + + @Test + public void releaseVmResourcesReleasesStorageForNoneHypervisor() throws Exception { + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Stopped); + when(vm.getHypervisorType()).thenReturn(HypervisorType.None); + + service.releaseVmResources(profile, false); + + verify(networkMgr).release(profile, false); + verify(volumeMgr).release(profile); + } + + @Test + public void cleanupForStartingVmWithNullWorkAndNullHostSkipsSendStop() throws Exception { + VirtualMachineGuru guru = mock(VirtualMachineGuru.class); + VirtualMachineProfile profile = mock(VirtualMachineProfile.class); + when(profile.getVirtualMachine()).thenReturn(vm); + when(vm.getState()).thenReturn(State.Starting); + when(vm.getHostId()).thenReturn(null); + + boolean result = service.cleanup(guru, profile, null, null, true); + + assertTrue(result); + verify(networkMgr).release(profile, true); + verify(virtualMachineManager, never()).sendStop(guru, profile, true, false); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmVlanPersistenceMappingServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmVlanPersistenceMappingServiceImplTest.java new file mode 100644 index 000000000000..2ed842449aa1 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmVlanPersistenceMappingServiceImplTest.java @@ -0,0 +1,207 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.api.query.dao.UserVmJoinDao; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.api.query.vo.UserVmJoinVO; +import com.cloud.network.Network; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmVlanPersistenceMappingServiceImplTest { + + private static final long VM_ID = 42L; + private static final long NETWORK_ID = 5L; + private static final long OFFERING_ID = 7L; + private static final String VLAN_ID = "123"; + private static final String VLAN_URI = "vlan://" + VLAN_ID; + + @InjectMocks + private VmVlanPersistenceMappingServiceImpl service; + + @Mock + private UserVmJoinDao userVmJoinDao; + @Mock + private DomainRouterJoinDao domainRouterJoinDao; + @Mock + private NetworkDao networkDao; + @Mock + private NetworkOfferingDao networkOfferingDao; + @Mock + private VMInstanceDao vmDao; + + @Test + public void userVmJoinPersistentVlanL2ReturnsFalse() { + stubUserVmNetwork(network(NETWORK_ID, OFFERING_ID, Network.GuestType.L2, VLAN_URI), offering(true)); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertEquals(1, result.size()); + assertFalse(result.get(VLAN_ID)); + } + + @Test + public void userVmJoinNonPersistentVlanReturnsTrue() { + stubUserVmNetwork(network(NETWORK_ID, OFFERING_ID, Network.GuestType.L2, VLAN_URI), offering(false)); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertEquals(1, result.size()); + assertTrue(result.get(VLAN_ID)); + } + + @Test + public void otherPersistentNetworkPreventsDelete() { + NetworkVO network = network(NETWORK_ID, OFFERING_ID, Network.GuestType.Shared, VLAN_URI); + stubUserVmNetwork(network, offering(false)); + when(networkDao.getOtherPersistentNetworksCount(NETWORK_ID, VLAN_URI, true)).thenReturn(1); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertEquals(1, result.size()); + assertFalse(result.get(VLAN_ID)); + } + + @Test + public void nullNetworkOrOfferingSkipped() { + UserVmJoinVO firstJoin = userVmJoin(NETWORK_ID); + UserVmJoinVO secondJoin = userVmJoin(NETWORK_ID + 1); + NetworkVO networkWithoutOffering = network(NETWORK_ID + 1, OFFERING_ID + 1, Network.GuestType.L2, VLAN_URI); + when(userVmJoinDao.searchByIds(VM_ID)).thenReturn(List.of(firstJoin, secondJoin)); + when(networkDao.findById(NETWORK_ID)).thenReturn(null); + when(networkDao.findById(NETWORK_ID + 1)).thenReturn(networkWithoutOffering); + when(networkOfferingDao.findById(OFFERING_ID + 1)).thenReturn(null); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertTrue(result.isEmpty()); + } + + @Test + public void domainRouterFallbackUsesRouterJoin() { + VMInstanceVO router = mock(VMInstanceVO.class); + DomainRouterJoinVO routerJoin = routerJoin(NETWORK_ID); + NetworkVO network = network(NETWORK_ID, OFFERING_ID, Network.GuestType.Isolated, VLAN_URI); + NetworkOfferingVO persistentOffering = offering(true); + when(userVmJoinDao.searchByIds(VM_ID)).thenReturn(List.of()); + when(vmDao.findById(VM_ID)).thenReturn(router); + when(router.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(domainRouterJoinDao.findById(VM_ID)).thenReturn(routerJoin); + when(networkDao.findById(NETWORK_ID)).thenReturn(network); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(persistentOffering); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertEquals(1, result.size()); + assertFalse(result.get(VLAN_ID)); + } + + @Test + public void domainRouterFallbackSkipsNullRouterJoin() { + VMInstanceVO router = mock(VMInstanceVO.class); + when(userVmJoinDao.searchByIds(VM_ID)).thenReturn(List.of()); + when(vmDao.findById(VM_ID)).thenReturn(router); + when(router.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(domainRouterJoinDao.findById(VM_ID)).thenReturn(null); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertTrue(result.isEmpty()); + verifyNoInteractions(networkOfferingDao); + } + + @Test + public void existingFalseIsNotOverwrittenByLaterTrue() { + UserVmJoinVO persistentJoin = userVmJoin(NETWORK_ID); + UserVmJoinVO nonPersistentJoin = userVmJoin(NETWORK_ID + 1); + NetworkVO persistentNetwork = network(NETWORK_ID, OFFERING_ID, Network.GuestType.L2, VLAN_URI); + NetworkVO nonPersistentNetwork = network(NETWORK_ID + 1, OFFERING_ID + 1, Network.GuestType.L2, VLAN_URI); + NetworkOfferingVO persistentOffering = offering(true); + NetworkOfferingVO nonPersistentOffering = offering(false); + when(userVmJoinDao.searchByIds(VM_ID)).thenReturn(List.of(persistentJoin, nonPersistentJoin)); + when(networkDao.findById(NETWORK_ID)).thenReturn(persistentNetwork); + when(networkDao.findById(NETWORK_ID + 1)).thenReturn(nonPersistentNetwork); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(persistentOffering); + when(networkOfferingDao.findById(OFFERING_ID + 1)).thenReturn(nonPersistentOffering); + + Map result = service.getVlanToPersistenceMapForVM(VM_ID); + + assertEquals(1, result.size()); + assertFalse(result.get(VLAN_ID)); + verify(networkOfferingDao).findById(OFFERING_ID + 1); + } + + private void stubUserVmNetwork(NetworkVO network, NetworkOfferingVO offering) { + UserVmJoinVO userVmJoin = userVmJoin(NETWORK_ID); + when(userVmJoinDao.searchByIds(VM_ID)).thenReturn(List.of(userVmJoin)); + when(networkDao.findById(NETWORK_ID)).thenReturn(network); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + } + + private UserVmJoinVO userVmJoin(long networkId) { + UserVmJoinVO userVmJoin = mock(UserVmJoinVO.class); + doReturn(networkId).when(userVmJoin).getNetworkId(); + return userVmJoin; + } + + private DomainRouterJoinVO routerJoin(long networkId) { + DomainRouterJoinVO routerJoin = mock(DomainRouterJoinVO.class); + doReturn(networkId).when(routerJoin).getNetworkId(); + return routerJoin; + } + + private NetworkVO network(long id, long offeringId, Network.GuestType guestType, String broadcastUri) { + NetworkVO network = mock(NetworkVO.class); + doReturn(id).when(network).getId(); + doReturn(offeringId).when(network).getNetworkOfferingId(); + doReturn(guestType).when(network).getGuestType(); + doReturn(URI.create(broadcastUri)).when(network).getBroadcastUri(); + return network; + } + + private NetworkOfferingVO offering(boolean persistent) { + NetworkOfferingVO offering = mock(NetworkOfferingVO.class); + doReturn(persistent).when(offering).isPersistent(); + return offering; + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmVolumeMigrationPlanningServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmVolumeMigrationPlanningServiceImplTest.java new file mode 100644 index 000000000000..eb64d1ed0a18 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmVolumeMigrationPlanningServiceImplTest.java @@ -0,0 +1,535 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.dc.dao.ClusterDao; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.DiskOfferingVO; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage; +import com.cloud.storage.StoragePool; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.DiskOfferingDao; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class VmVolumeMigrationPlanningServiceImplTest { + + @InjectMocks + private VmVolumeMigrationPlanningServiceImpl service; + + @Mock + private VolumeDao volumeDao; + @Mock + private PrimaryDataStoreDao storagePoolDao; + @Mock + private StoragePoolHostDao poolHostDao; + @Mock + private HostDao hostDao; + @Mock + private ClusterDao clusterDao; + @Mock + private DiskOfferingDao diskOfferingDao; + + @Mock + private VirtualMachineProfile profileMock; + @Mock + private StoragePoolVO currentPoolMock; + @Mock + private VolumeVO volumeMock; + @Mock + private HostVO hostMock; + + private final long hostId = 1L; + private final long currentPoolId = 10L; + private final long targetPoolId = 20L; + private final long clusterAId = 100L; + private final long clusterBId = 200L; + + @Before + public void setUp() { + when(hostMock.getId()).thenReturn(hostId); + when(currentPoolMock.getId()).thenReturn(currentPoolId); + when(volumeMock.getPoolId()).thenReturn(currentPoolId); + when(volumeMock.getUuid()).thenReturn(UUID.randomUUID().toString()); + when(currentPoolMock.getUuid()).thenReturn(UUID.randomUUID().toString()); + + List allocators = new ArrayList<>(); + service.setStoragePoolAllocators(allocators); + } + + // ------------------------------------------------------------------------- + // isStorageCrossClusterMigration (4 cases) + // ------------------------------------------------------------------------- + + @Test + public void isStorageCrossClusterMigration_nullClusterId_returnsFalse() { + assertFalse(service.isStorageCrossClusterMigration(null, currentPoolMock)); + verify(currentPoolMock, never()).getScope(); + } + + @Test + public void isStorageCrossClusterMigration_nonClusterScopePool_returnsFalse() { + when(currentPoolMock.getScope()).thenReturn(ScopeType.ZONE); + assertFalse(service.isStorageCrossClusterMigration(clusterAId, currentPoolMock)); + } + + @Test + public void isStorageCrossClusterMigration_sameCluster_returnsFalse() { + when(currentPoolMock.getScope()).thenReturn(ScopeType.CLUSTER); + when(currentPoolMock.getClusterId()).thenReturn(clusterAId); + assertFalse(service.isStorageCrossClusterMigration(clusterAId, currentPoolMock)); + } + + @Test + public void isStorageCrossClusterMigration_differentCluster_returnsTrue() { + when(currentPoolMock.getScope()).thenReturn(ScopeType.CLUSTER); + when(currentPoolMock.getClusterId()).thenReturn(clusterBId); + assertTrue(service.isStorageCrossClusterMigration(clusterAId, currentPoolMock)); + } + + // ------------------------------------------------------------------------- + // executeManagedStorageChecksWhenTargetStoragePoolProvided (3 cases) + // ------------------------------------------------------------------------- + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolProvided_nonManaged_noOp() { + StoragePoolVO targetPool = mock(StoragePoolVO.class); + when(currentPoolMock.isManaged()).thenReturn(false); + // Should not throw + service.executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPoolMock, volumeMock, targetPool); + } + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolProvided_managedSamePool_noOp() { + when(currentPoolMock.isManaged()).thenReturn(true); + when(currentPoolMock.getPoolType()).thenReturn(Storage.StoragePoolType.RBD); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + when(targetPool.getId()).thenReturn(currentPoolId); + // Same pool id — should not throw + service.executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPoolMock, volumeMock, targetPool); + } + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolProvided_managedDifferentPool_throws() { + when(currentPoolMock.isManaged()).thenReturn(true); + when(currentPoolMock.getPoolType()).thenReturn(Storage.StoragePoolType.RBD); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + when(targetPool.getId()).thenReturn(targetPoolId); + when(storagePoolDao.getDetails(currentPoolId)).thenReturn(null); + + assertThrows(CloudRuntimeException.class, () -> + service.executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPoolMock, volumeMock, targetPool)); + } + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolProvided_powerFlex_noOp() { + when(currentPoolMock.isManaged()).thenReturn(true); + when(currentPoolMock.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + + service.executeManagedStorageChecksWhenTargetStoragePoolProvided(currentPoolMock, volumeMock, targetPool); + + verify(targetPool, never()).getId(); + } + + // ------------------------------------------------------------------------- + // executeManagedStorageChecksWhenTargetStoragePoolNotProvided (3 cases) + // ------------------------------------------------------------------------- + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolNotProvided_nonManaged_noOp() { + when(currentPoolMock.isManaged()).thenReturn(false); + // Should not throw + service.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, currentPoolMock, volumeMock); + } + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolNotProvided_managedConnected_noOp() { + when(currentPoolMock.isManaged()).thenReturn(true); + when(poolHostDao.findByPoolHost(currentPoolId, hostId)).thenReturn(mock(com.cloud.storage.StoragePoolHostVO.class)); + // Has access — should not throw + service.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, currentPoolMock, volumeMock); + } + + @Test + public void executeManagedStorageChecksWhenTargetStoragePoolNotProvided_managedDisconnected_throws() { + when(currentPoolMock.isManaged()).thenReturn(true); + when(poolHostDao.findByPoolHost(currentPoolId, hostId)).thenReturn(null); + when(hostMock.getUuid()).thenReturn(UUID.randomUUID().toString()); + + assertThrows(CloudRuntimeException.class, () -> + service.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, currentPoolMock, volumeMock)); + } + + // ------------------------------------------------------------------------- + // findVolumesThatWereNotMappedByTheUser (2 cases) + // ------------------------------------------------------------------------- + + @Test + public void findVolumesThatWereNotMappedByTheUser_partialMap_returnsUnmapped() { + VolumeVO vol1 = mock(VolumeVO.class); + VolumeVO vol2 = mock(VolumeVO.class); + List allVolumes = new ArrayList<>(); + allVolumes.add(vol1); + allVolumes.add(vol2); + + when(profileMock.getId()).thenReturn(1L); + when(volumeDao.findUsableVolumesForInstance(1L)).thenReturn(allVolumes); + + Map alreadyMapped = new HashMap<>(); + alreadyMapped.put(vol1, mock(StoragePool.class)); + + List unmapped = service.findVolumesThatWereNotMappedByTheUser(profileMock, alreadyMapped); + assertEquals(1, unmapped.size()); + assertTrue(unmapped.contains(vol2)); + } + + @Test + public void findVolumesThatWereNotMappedByTheUser_emptyMap_returnsAll() { + VolumeVO vol1 = mock(VolumeVO.class); + VolumeVO vol2 = mock(VolumeVO.class); + List allVolumes = new ArrayList<>(); + allVolumes.add(vol1); + allVolumes.add(vol2); + + when(profileMock.getId()).thenReturn(2L); + when(volumeDao.findUsableVolumesForInstance(2L)).thenReturn(allVolumes); + + List unmapped = service.findVolumesThatWereNotMappedByTheUser(profileMock, new HashMap<>()); + assertEquals(2, unmapped.size()); + } + + // ------------------------------------------------------------------------- + // buildMapUsingUserInformation (2 cases) + // ------------------------------------------------------------------------- + + @Test + public void buildMapUsingUserInformation_emptyInput_returnsEmptyMap() { + Map result = service.buildMapUsingUserInformation(profileMock, hostMock, new HashMap<>()); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void buildMapUsingUserInformation_validEntry_addsToMap() { + long volumeId = 55L; + long poolId = 66L; + VolumeVO vol = mock(VolumeVO.class); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + StoragePoolVO currentPool = mock(StoragePoolVO.class); + + when(vol.getPoolId()).thenReturn(poolId); + when(vol.getUuid()).thenReturn(UUID.randomUUID().toString()); + when(targetPool.getId()).thenReturn(poolId); + when(currentPool.getId()).thenReturn(poolId); + when(currentPool.isManaged()).thenReturn(false); + when(targetPool.getId()).thenReturn(poolId); + + when(volumeDao.findById(volumeId)).thenReturn(vol); + when(storagePoolDao.findById(poolId)).thenReturn(targetPool).thenReturn(currentPool); + // Host access check — pool accessible + when(poolHostDao.findByPoolHost(anyLong(), anyLong())).thenReturn(mock(com.cloud.storage.StoragePoolHostVO.class)); + + Map userMap = new HashMap<>(); + userMap.put(volumeId, poolId); + + Map result = service.buildMapUsingUserInformation(profileMock, hostMock, userMap); + assertFalse(result.isEmpty()); + assertTrue(result.containsKey(vol)); + } + + @Test + public void createMappingVolumeAndStoragePool_planWithoutHostMapsUserDefinedVolumesWithoutHostAccessCheck() { + long volumeId = 77L; + long poolId = 88L; + long vmId = 99L; + VolumeVO vol = mock(VolumeVO.class); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + StoragePoolVO currentPool = mock(StoragePoolVO.class); + DataCenterDeployment plan = new DataCenterDeployment(1L, 2L, 3L, null, null, null); + + when(profileMock.getId()).thenReturn(vmId); + when(vol.getPoolId()).thenReturn(currentPoolId); + when(targetPool.getId()).thenReturn(poolId); + when(currentPool.getId()).thenReturn(currentPoolId); + when(currentPool.isManaged()).thenReturn(false); + when(volumeDao.findById(volumeId)).thenReturn(vol); + when(volumeDao.findUsableVolumesForInstance(vmId)).thenReturn(List.of(vol)); + when(storagePoolDao.findById(poolId)).thenReturn(targetPool); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPool); + + Map userMap = new HashMap<>(); + userMap.put(volumeId, poolId); + + Map result = service.createMappingVolumeAndStoragePool(profileMock, plan, userMap); + + assertEquals(1, result.size()); + assertEquals(targetPool, result.get(vol)); + verify(poolHostDao, never()).findByPoolHost(anyLong(), anyLong()); + } + + @Test + public void shouldMapVolume_kvmUnmanaged_returnsFalse() { + when(profileMock.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(currentPoolMock.isManaged()).thenReturn(false); + + assertFalse(service.shouldMapVolume(profileMock, currentPoolMock)); + } + + @Test + public void shouldMapVolume_kvmManaged_returnsTrue() { + when(profileMock.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(currentPoolMock.isManaged()).thenReturn(true); + + assertTrue(service.shouldMapVolume(profileMock, currentPoolMock)); + } + + @Test + public void shouldMapVolume_vmwareUnmanaged_returnsTrue() { + when(profileMock.getHypervisorType()).thenReturn(HypervisorType.VMware); + when(currentPoolMock.isManaged()).thenReturn(false); + + assertTrue(service.shouldMapVolume(profileMock, currentPoolMock)); + } + + @Test + public void createStoragePoolMappingsForVolumes_hostScopeDelegatesToCandidateMapping() { + DataCenterDeployment plan = new DataCenterDeployment(1L, 2L, clusterAId, hostId, null, null); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + String targetPoolUuid = UUID.randomUUID().toString(); + Map mapped = new HashMap<>(); + + when(hostDao.findById(hostId)).thenReturn(hostMock); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isManaged()).thenReturn(false); + when(currentPoolMock.getScope()).thenReturn(ScopeType.HOST); + configureSingleLocalCandidate(targetPool, targetPoolId, targetPoolUuid); + when(storagePoolDao.findByUuid(targetPoolUuid)).thenReturn(targetPool); + + service.createStoragePoolMappingsForVolumes(profileMock, plan, mapped, List.of(volumeMock)); + + assertEquals(targetPool, mapped.get(volumeMock)); + } + + @Test + public void createStoragePoolMappingsForVolumes_clusterSameClusterKvmUnmanagedSkipsMapping() { + DataCenterDeployment plan = new DataCenterDeployment(1L, 2L, clusterAId, null, null, null); + Map mapped = new HashMap<>(); + + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isManaged()).thenReturn(false); + when(currentPoolMock.getScope()).thenReturn(ScopeType.CLUSTER); + when(currentPoolMock.getClusterId()).thenReturn(clusterAId); + when(profileMock.getHypervisorType()).thenReturn(HypervisorType.KVM); + + service.createStoragePoolMappingsForVolumes(profileMock, plan, mapped, List.of(volumeMock)); + + assertTrue(mapped.isEmpty()); + } + + @Test + public void createVolumeToStoragePoolMappingIfPossible_emptyCandidatesForHostThrows() { + DataCenterDeployment plan = new DataCenterDeployment(1L, 2L, clusterAId, hostId, null, null); + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + + when(volumeMock.getDiskOfferingId()).thenReturn(5L); + when(diskOfferingDao.findById(5L)).thenReturn(diskOffering); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isLocal()).thenReturn(false); + when(profileMock.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(hostDao.findById(hostId)).thenReturn(hostMock); + when(hostMock.getUuid()).thenReturn(UUID.randomUUID().toString()); + + assertThrows(CloudRuntimeException.class, () -> + service.createVolumeToStoragePoolMappingIfPossible(profileMock, plan, new HashMap<>(), volumeMock, currentPoolMock)); + } + + @Test + public void createVolumeToStoragePoolMappingIfPossible_currentPoolCandidateLeavesMapEmpty() { + DataCenterDeployment plan = new DataCenterDeployment(1L, 2L, clusterAId, hostId, null, null); + Map mapped = new HashMap<>(); + configureSingleLocalCandidate(currentPoolMock, currentPoolId, UUID.randomUUID().toString()); + + service.createVolumeToStoragePoolMappingIfPossible(profileMock, plan, mapped, volumeMock, currentPoolMock); + + assertTrue(mapped.isEmpty()); + } + + @Test + public void createVolumeToStoragePoolMappingIfPossible_differentCandidateMapsByUuid() { + DataCenterDeployment plan = new DataCenterDeployment(1L, 2L, clusterAId, hostId, null, null); + StoragePoolVO targetPool = mock(StoragePoolVO.class); + String targetPoolUuid = UUID.randomUUID().toString(); + Map mapped = new HashMap<>(); + + configureSingleLocalCandidate(targetPool, targetPoolId, targetPoolUuid); + when(storagePoolDao.findByUuid(targetPoolUuid)).thenReturn(targetPool); + + service.createVolumeToStoragePoolMappingIfPossible(profileMock, plan, mapped, volumeMock, currentPoolMock); + + assertEquals(targetPool, mapped.get(volumeMock)); + } + + @Test + public void createMappingVolumeAndStoragePool_hostOverloadUsesHostPlan() { + long vmId = 99L; + long zoneId = 7L; + long podId = 8L; + StoragePoolVO targetPool = mock(StoragePoolVO.class); + String targetPoolUuid = UUID.randomUUID().toString(); + + when(profileMock.getId()).thenReturn(vmId); + when(hostMock.getDataCenterId()).thenReturn(zoneId); + when(hostMock.getPodId()).thenReturn(podId); + when(hostMock.getClusterId()).thenReturn(clusterAId); + when(hostMock.getId()).thenReturn(hostId); + when(hostDao.findById(hostId)).thenReturn(hostMock); + when(volumeDao.findUsableVolumesForInstance(vmId)).thenReturn(List.of(volumeMock)); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isManaged()).thenReturn(false); + when(currentPoolMock.getScope()).thenReturn(ScopeType.HOST); + configureSingleLocalCandidate(targetPool, targetPoolId, targetPoolUuid, zoneId, podId, clusterAId, hostId); + when(storagePoolDao.findByUuid(targetPoolUuid)).thenReturn(targetPool); + + Map result = service.createMappingVolumeAndStoragePool(profileMock, hostMock, new HashMap<>()); + + assertEquals(targetPool, result.get(volumeMock)); + } + + // ------------------------------------------------------------------------- + // getCandidateStoragePoolsToMigrateLocalVolume (2 cases) + // ------------------------------------------------------------------------- + + @Test + public void getCandidateStoragePoolsToMigrateLocalVolume_localVolume_includesLocalPools() { + StoragePoolAllocator allocator = mock(StoragePoolAllocator.class); + List allocators = new ArrayList<>(); + allocators.add(allocator); + service.setStoragePoolAllocators(allocators); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + StoragePool localPool = mock(StoragePool.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + + when(volumeMock.getDiskOfferingId()).thenReturn(5L); + when(diskOfferingDao.findById(5L)).thenReturn(diskOffering); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isLocal()).thenReturn(true); + when(profileMock.getHypervisorType()).thenReturn(com.cloud.hypervisor.Hypervisor.HypervisorType.KVM); + + List fromAllocator = new ArrayList<>(); + fromAllocator.add(localPool); + when(localPool.isLocal()).thenReturn(true); + when(allocator.allocateToPool(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyInt())) + .thenReturn(fromAllocator); + + List result = service.getCandidateStoragePoolsToMigrateLocalVolume(profileMock, plan, volumeMock); + assertFalse(result.isEmpty()); + assertTrue(result.contains(localPool)); + } + + @Test + public void getCandidateStoragePoolsToMigrateLocalVolume_noAllocators_returnsEmpty() { + service.setStoragePoolAllocators(new ArrayList<>()); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + DataCenterDeployment plan = mock(DataCenterDeployment.class); + + when(volumeMock.getDiskOfferingId()).thenReturn(6L); + when(diskOfferingDao.findById(6L)).thenReturn(diskOffering); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isLocal()).thenReturn(false); + when(profileMock.getHypervisorType()).thenReturn(com.cloud.hypervisor.Hypervisor.HypervisorType.KVM); + + List result = service.getCandidateStoragePoolsToMigrateLocalVolume(profileMock, plan, volumeMock); + assertTrue(result.isEmpty()); + } + + private void configureSingleLocalCandidate(StoragePool candidatePool, long candidatePoolId, String candidatePoolUuid) { + configureSingleLocalCandidate(candidatePool, candidatePoolId, candidatePoolUuid, null, null, null, null); + } + + private void configureSingleLocalCandidate(StoragePool candidatePool, long candidatePoolId, String candidatePoolUuid, Long expectedZoneId, Long expectedPodId, + Long expectedClusterId, Long expectedHostId) { + StoragePoolAllocator allocator = mock(StoragePoolAllocator.class); + service.setStoragePoolAllocators(List.of(allocator)); + + DiskOfferingVO diskOffering = mock(DiskOfferingVO.class); + when(volumeMock.getDiskOfferingId()).thenReturn(5L); + when(diskOfferingDao.findById(5L)).thenReturn(diskOffering); + when(storagePoolDao.findById(currentPoolId)).thenReturn(currentPoolMock); + when(currentPoolMock.isLocal()).thenReturn(true); + when(profileMock.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(candidatePool.getId()).thenReturn(candidatePoolId); + when(candidatePool.getUuid()).thenReturn(candidatePoolUuid); + when(candidatePool.isLocal()).thenReturn(true); + when(allocator.allocateToPool(Mockito.any(), Mockito.any(), Mockito.any(DeploymentPlan.class), Mockito.any(), Mockito.anyInt())) + .thenAnswer(invocation -> { + DeploymentPlan plan = invocation.getArgument(2); + if (expectedZoneId != null) { + assertTrue(expectedZoneId.longValue() == plan.getDataCenterId()); + } + if (expectedPodId != null) { + assertTrue(expectedPodId.longValue() == plan.getPodId()); + } + if (expectedClusterId != null) { + assertTrue(expectedClusterId.longValue() == plan.getClusterId()); + } + if (expectedHostId != null) { + assertTrue(expectedHostId.longValue() == plan.getHostId()); + } + return List.of(candidatePool); + }); + } +} diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VmWorkJobQueueServiceImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VmWorkJobQueueServiceImplTest.java new file mode 100644 index 000000000000..d63de5dabf78 --- /dev/null +++ b/engine/orchestration/src/test/java/com/cloud/vm/VmWorkJobQueueServiceImplTest.java @@ -0,0 +1,381 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; +import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; +import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.jobs.AsyncJob; +import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.framework.jobs.AsyncJobManager; +import org.apache.cloudstack.framework.jobs.Outcome; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.utils.identity.ManagementServerNode; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.dc.DataCenter; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.org.Cluster; +import com.cloud.storage.VolumeApiService; +import com.cloud.user.Account; +import com.cloud.user.User; +import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.dao.VMInstanceDao; + +@RunWith(MockitoJUnitRunner.class) +public class VmWorkJobQueueServiceImplTest { + + private static final long VM_ID = 42L; + private static final String VM_UUID = "vm-uuid"; + private static final long ACCOUNT_ID = 11L; + private static final long USER_ID = 12L; + private static final String CONTEXT_ID = "context-id"; + private static final String ORIGIN_JOB_ID = "origin-job-id"; + + @InjectMocks + private VmWorkJobQueueServiceImpl service; + + @Mock + private EntityManager entityMgr; + @Mock + private VMInstanceDao vmDao; + @Mock + private VmWorkJobDao workJobDao; + @Mock + private AsyncJobManager jobMgr; + @Mock + private PrimaryDataStoreDao storagePoolDao; + @Mock + private AsyncJobExecutionContext jobExecutionContext; + @Mock + private CallContext callContext; + @Mock + private Account account; + @Mock + private User user; + + @Before + public void setUp() { + when(callContext.getCallingAccount()).thenReturn(account); + when(callContext.getCallingUser()).thenReturn(user); + when(callContext.getContextId()).thenReturn(CONTEXT_ID); + when(account.getId()).thenReturn(ACCOUNT_ID); + when(user.getId()).thenReturn(USER_ID); + } + + @Test + public void createPlaceHolderWorkSetsPlaceholderFieldsAndPersists() { + try (MockedStatic managementServerNode = mockStatic(ManagementServerNode.class)) { + managementServerNode.when(ManagementServerNode::getManagementServerId).thenReturn(123L); + + VmWorkJobVO job = service.createPlaceHolderWork(VM_ID); + + assertEquals(VmWorkConstants.VM_WORK_JOB_PLACEHOLDER, job.getDispatcher()); + assertEquals("", job.getCmd()); + assertEquals("", job.getCmdInfo()); + assertEquals(0L, job.getAccountId()); + assertEquals(0L, job.getUserId()); + assertEquals(VmWorkJobVO.Step.Starting, job.getStep()); + assertEquals(VirtualMachine.Type.Instance, job.getVmType()); + assertEquals(VM_ID, job.getVmInstanceId()); + assertEquals(Long.valueOf(123L), job.getInitMsid()); + assertNull(job.getSecondaryObjectIdentifier()); + verify(workJobDao).persist(job); + } + } + + @Test + public void createPlaceHolderWorkSetsSecondaryObjectIdentifierWhenPresent() { + try (MockedStatic managementServerNode = mockStatic(ManagementServerNode.class)) { + managementServerNode.when(ManagementServerNode::getManagementServerId).thenReturn(123L); + + VmWorkJobVO job = service.createPlaceHolderWork(VM_ID, "network-uuid"); + + assertEquals("network-uuid", job.getSecondaryObjectIdentifier()); + verify(workJobDao).persist(job); + } + } + + @Test + public void expungePlaceHolderWorkNoOpsForNullAndExpungesNonNull() { + service.expungePlaceHolderWork(null); + verify(workJobDao, never()).expunge(anyLong()); + + VmWorkJobVO job = new VmWorkJobVO(""); + job.setId(55L); + service.expungePlaceHolderWork(job); + + verify(workJobDao).expunge(55L); + } + + @Test + public void retrievePendingWorkJobResolvesVmAndReturnsFirstPendingJob() { + VMInstanceVO vm = mock(VMInstanceVO.class); + VmWorkJobVO firstJob = new VmWorkJobVO(""); + VmWorkJobVO secondJob = new VmWorkJobVO(""); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.DomainRouter, VM_ID, VmWorkStart.class.getName())) + .thenReturn(Arrays.asList(firstJob, secondJob)); + + Pair result = service.retrievePendingWorkJob(null, VM_UUID, null, VmWorkStart.class.getName()); + + assertSame(firstJob, result.first()); + assertEquals(Long.valueOf(VM_ID), result.second()); + } + + @Test + public void retrievePendingWorkJobThrowsExistingMessageWhenVmUuidLookupFails() { + when(vmDao.findByUuid(VM_UUID)).thenReturn(null); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.retrievePendingWorkJob(VM_UUID, VmWorkStart.class.getName())); + + assertEquals("Could not find a VM with the uuid [vm-uuid]. Unable to continue validations with command [com.cloud.vm.VmWorkStart] through job queue.", + exception.getMessage()); + } + + @Test + public void createWorkJobAndWorkInfoCopiesContextAndUsesVirtualMachineManagerHandler() { + try (MockedStatic callContextStatic = mockStatic(CallContext.class); + MockedStatic asyncJobExecutionContext = mockStatic(AsyncJobExecutionContext.class)) { + callContextStatic.when(CallContext::current).thenReturn(callContext); + asyncJobExecutionContext.when(AsyncJobExecutionContext::getOriginJobId).thenReturn(ORIGIN_JOB_ID); + + Pair result = service.createWorkJobAndWorkInfo( + VmWorkStop.class.getName(), VmWorkJobVO.Step.Prepare, VM_ID); + + VmWorkJobVO job = result.first(); + assertEquals(VmWorkConstants.VM_WORK_JOB_DISPATCHER, job.getDispatcher()); + assertEquals(VmWorkStop.class.getName(), job.getCmd()); + assertEquals(ACCOUNT_ID, job.getAccountId()); + assertEquals(USER_ID, job.getUserId()); + assertEquals(VmWorkJobVO.Step.Prepare, job.getStep()); + assertEquals(VirtualMachine.Type.Instance, job.getVmType()); + assertEquals(VM_ID, job.getVmInstanceId()); + assertEquals(ORIGIN_JOB_ID, job.getRelated()); + + VmWork work = result.second(); + assertEquals(USER_ID, work.getUserId()); + assertEquals(ACCOUNT_ID, work.getAccountId()); + assertEquals(VM_ID, work.getVmId()); + assertEquals(VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER, work.getHandlerName()); + } + } + + @Test + public void setCmdInfoAndSubmitAsyncJobSerializesWorkAndSubmitsToVmWorkQueue() { + VmWorkJobVO job = new VmWorkJobVO(""); + VmWork work = new VmWork(USER_ID, ACCOUNT_ID, VM_ID, VirtualMachineManagerImpl.VM_WORK_JOB_HANDLER); + + service.setCmdInfoAndSubmitAsyncJob(job, work, VM_ID); + + assertEquals(work.getVmId(), VmWorkSerializer.deserialize(VmWork.class, job.getCmdInfo()).getVmId()); + verify(jobMgr).submitAsyncJob(job, VmWorkConstants.VM_WORK_QUEUE, VM_ID); + } + + @Test + public void retrieveResultFromJobOutcomeReturnsNullAndPlainResult() throws Exception { + Outcome outcome = mockOutcome(); + when(jobMgr.unmarshallResultObject(outcome.getJob())).thenReturn(null, "ok"); + + assertNull(service.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome)); + assertEquals("ok", service.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome)); + } + + @Test + public void retrieveResultFromJobOutcomeRethrowsExpectedExceptionTypes() { + assertRethrown(new AgentUnavailableException("agent", 1L), AgentUnavailableException.class); + assertRethrown(new InsufficientServerCapacityException("capacity", Cluster.class, 2L), InsufficientServerCapacityException.class); + assertRethrown(new ResourceUnavailableException("resource", DataCenter.class, 3L), ResourceUnavailableException.class); + assertRethrown(new InsufficientCapacityException("capacity", DataCenter.class, 4L) {}, InsufficientCapacityException.class); + assertRethrown(new ConcurrentOperationException("concurrent"), ConcurrentOperationException.class); + assertRethrown(new IllegalStateException("runtime"), IllegalStateException.class); + } + + @Test + public void retrieveResultFromJobOutcomeWrapsGenericThrowable() { + Outcome outcome = mockOutcome(); + Exception cause = new Exception("checked"); + when(jobMgr.unmarshallResultObject(outcome.getJob())).thenReturn(cause); + + RuntimeException exception = assertThrows(RuntimeException.class, + () -> service.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome)); + + assertEquals("Unexpected exception", exception.getMessage()); + assertSame(cause, exception.getCause()); + } + + @Test + public void addVmToNetworkThroughJobQueueReusesMatchingPendingJob() { + VirtualMachine vm = mock(VirtualMachine.class); + Network network = mock(Network.class); + VmWorkJobVO pendingJob = new VmWorkJobVO(""); + pendingJob.setId(77L); + when(vm.getId()).thenReturn(VM_ID); + when(network.getUuid()).thenReturn("network-uuid"); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID, VmWorkAddVmToNetwork.class.getName())) + .thenReturn(Collections.emptyList()); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID, VmWorkAddVmToNetwork.class.getName(), "network-uuid")) + .thenReturn(Collections.singletonList(pendingJob)); + + try (MockedStatic callContextStatic = mockStatic(CallContext.class); + MockedStatic asyncJobExecutionContext = mockStatic(AsyncJobExecutionContext.class)) { + callContextStatic.when(CallContext::current).thenReturn(callContext); + asyncJobExecutionContext.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobExecutionContext); + + Outcome outcome = service.addVmToNetworkThroughJobQueue(vm, network, mock(NicProfile.class)); + + assertSame(pendingJob, ReflectionTestUtils.getField(outcome, "_job")); + verify(jobExecutionContext).joinJob(77L); + verify(jobMgr, never()).submitAsyncJob(any(), eq(VmWorkConstants.VM_WORK_QUEUE), eq(VM_ID)); + } + } + + @Test + public void addVmToNetworkThroughJobQueueThrowsExistingDuplicateJobMessage() { + VirtualMachine vm = mock(VirtualMachine.class); + Network network = mock(Network.class); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getInstanceName()).thenReturn("vm-name"); + when(network.getUuid()).thenReturn("network-uuid"); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID, VmWorkAddVmToNetwork.class.getName())) + .thenReturn(Collections.emptyList()); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID, VmWorkAddVmToNetwork.class.getName(), "network-uuid")) + .thenReturn(Arrays.asList(new VmWorkJobVO(""), new VmWorkJobVO(""))); + + try (MockedStatic callContextStatic = mockStatic(CallContext.class)) { + callContextStatic.when(CallContext::current).thenReturn(callContext); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + () -> service.addVmToNetworkThroughJobQueue(vm, network, mock(NicProfile.class))); + + assertEquals("The number of jobs to add network network-uuid to vm vm-name are 2", exception.getMessage()); + } + } + + @Test + public void migrateVmStorageThroughJobQueueChecksEachUniquePoolOnceBeforeSubmitting() throws Exception { + VMInstanceVO vm = mock(VMInstanceVO.class); + StoragePoolVO poolOne = mock(StoragePoolVO.class); + StoragePoolVO poolTwo = mock(StoragePoolVO.class); + when(vmDao.findByUuid(VM_UUID)).thenReturn(vm); + when(vm.getId()).thenReturn(VM_ID); + when(storagePoolDao.findById(100L)).thenReturn(poolOne); + when(storagePoolDao.findById(200L)).thenReturn(poolTwo); + when(poolOne.getUuid()).thenReturn("pool-one"); + when(poolTwo.getUuid()).thenReturn("pool-two"); + when(workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, VM_ID, VmWorkStorageMigration.class.getName())) + .thenReturn(Collections.emptyList()); + + Map volumeToPool = new HashMap<>(); + volumeToPool.put(1L, 100L); + volumeToPool.put(2L, 100L); + volumeToPool.put(3L, 200L); + + Object originalThresholdValue = setConfigKeyValue(VolumeApiService.ConcurrentMigrationsThresholdPerDatastore, null); + Object originalThresholdDefault = setConfigKeyField(VolumeApiService.ConcurrentMigrationsThresholdPerDatastore, "_defaultValue", "1"); + try (MockedStatic callContextStatic = mockStatic(CallContext.class); + MockedStatic asyncJobExecutionContext = mockStatic(AsyncJobExecutionContext.class)) { + callContextStatic.when(CallContext::current).thenReturn(callContext); + asyncJobExecutionContext.when(AsyncJobExecutionContext::getOriginJobId).thenReturn(ORIGIN_JOB_ID); + asyncJobExecutionContext.when(AsyncJobExecutionContext::getCurrentExecutionContext).thenReturn(jobExecutionContext); + + service.migrateVmStorageThroughJobQueue(VM_UUID, volumeToPool); + + verify(storagePoolDao).findById(100L); + verify(storagePoolDao).findById(200L); + verify(jobMgr).countPendingJobs("\"storageid\":\"pool-one\"", MigrateVMCmd.class.getName(), MigrateVolumeCmd.class.getName(), MigrateVolumeCmdByAdmin.class.getName()); + verify(jobMgr).countPendingJobs("\"storageid\":\"pool-two\"", MigrateVMCmd.class.getName(), MigrateVolumeCmd.class.getName(), MigrateVolumeCmdByAdmin.class.getName()); + ArgumentCaptor jobCaptor = ArgumentCaptor.forClass(VmWorkJobVO.class); + verify(jobMgr).submitAsyncJob(jobCaptor.capture(), eq(VmWorkConstants.VM_WORK_QUEUE), eq(VM_ID)); + VmWorkStorageMigration work = VmWorkSerializer.deserialize(VmWorkStorageMigration.class, jobCaptor.getValue().getCmdInfo()); + assertEquals(volumeToPool, work.getVolumeToPool()); + } finally { + setConfigKeyValue(VolumeApiService.ConcurrentMigrationsThresholdPerDatastore, originalThresholdValue); + setConfigKeyField(VolumeApiService.ConcurrentMigrationsThresholdPerDatastore, "_defaultValue", originalThresholdDefault); + } + } + + private Outcome mockOutcome() { + Outcome outcome = mock(Outcome.class); + AsyncJob job = mock(AsyncJob.class); + when(outcome.getJob()).thenReturn(job); + return outcome; + } + + private void assertRethrown(T throwable, Class expectedType) { + Outcome outcome = mockOutcome(); + when(jobMgr.unmarshallResultObject(outcome.getJob())).thenReturn(throwable); + + assertSame(throwable, assertThrows(expectedType, + () -> service.retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome))); + } + + private Object setConfigKeyValue(final ConfigKey configKey, final Object value) throws Exception { + return setConfigKeyField(configKey, "_value", value); + } + + private Object setConfigKeyField(final ConfigKey configKey, final String fieldName, final Object value) throws Exception { + Field valueField = ConfigKey.class.getDeclaredField(fieldName); + valueField.setAccessible(true); + Object originalValue = valueField.get(configKey); + valueField.set(configKey, value); + return originalValue; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationServiceImplTest.java new file mode 100644 index 000000000000..e70b38211be3 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/GuestNetworkCreationPreparationServiceImplTest.java @@ -0,0 +1,324 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.Vlan; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.domain.Domain; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.PVlanType; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.offering.NetworkOffering.Availability; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; + +public class GuestNetworkCreationPreparationServiceImplTest { + + private static final long OFFERING_ID = 10L; + private static final long ZONE_ID = 20L; + private static final long PHYSICAL_NETWORK_ID = 30L; + private static final long ACCOUNT_ID = 40L; + + private GuestNetworkCreationPreparationServiceImpl service; + private NetworkOfferingDao networkOfferingDao; + private DataCenterDao dcDao; + private NetworkDao networksDao; + private NetworkModel networkModel; + private EntityManager entityManager; + private NetworkOfferingVlanValidationService vlanValidationService; + private NetworkOfferingVO offering; + private DataCenterVO zone; + private PhysicalNetworkVO physicalNetwork; + private Account owner; + + @Before + public void setUp() { + networkOfferingDao = mock(NetworkOfferingDao.class); + dcDao = mock(DataCenterDao.class); + networksDao = mock(NetworkDao.class); + networkModel = mock(NetworkModel.class); + entityManager = mock(EntityManager.class); + vlanValidationService = mock(NetworkOfferingVlanValidationService.class); + + service = new GuestNetworkCreationPreparationServiceImpl(); + service.networkOfferingDao = networkOfferingDao; + service.dcDao = dcDao; + service.networksDao = networksDao; + service.networkModel = networkModel; + service.entityMgr = entityManager; + service.networkOfferingVlanValidationService = vlanValidationService; + + offering = newOffering(GuestType.Shared, true); + zone = new DataCenterVO(ZONE_ID, "zone", "zone", "8.8.8.8", "8.8.4.4", "10.0.0.1", "10.0.0.2", "10.1.0.0/16", null, null, + NetworkType.Advanced, "token", "example.com"); + physicalNetwork = new PhysicalNetworkVO(PHYSICAL_NETWORK_ID, ZONE_ID, null, null, null, null, "physical-network"); + physicalNetwork.setState(com.cloud.network.PhysicalNetwork.State.Enabled); + physicalNetwork.setIsolationMethods(Collections.singletonList("vlan")); + owner = mock(Account.class); + when(owner.getId()).thenReturn(ACCOUNT_ID); + when(owner.getAccountId()).thenReturn(ACCOUNT_ID); + + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(dcDao.findById(ZONE_ID)).thenReturn(zone); + when(networksDao.listByZoneAndTrafficType(ZONE_ID, TrafficType.Guest)).thenReturn(Collections.emptyList()); + when(networksDao.listByPhysicalNetworkPvlan(anyLong(), anyString())).thenReturn(Collections.emptyList()); + when(networksDao.listByPhysicalNetworkPvlan(anyLong(), anyString(), any(PVlanType.class))).thenReturn(Collections.emptyList()); + when(entityManager.findById(NetworkOffering.class, OFFERING_ID)).thenReturn(offering); + when(vlanValidationService.encodeVlanIdIntoBroadcastUri(anyString(), eq(physicalNetwork))).thenReturn(URI.create("vlan://123")); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsDisabledOffering() { + offering.setState(NetworkOffering.State.Disabled); + + prepare(); + } + + @Test + public void returnsNullForNonGuestOffering() { + NetworkOfferingVO managementOffering = new NetworkOfferingVO("management", "management", TrafficType.Management, false, false, 0, 0, false, + Availability.Optional, null, GuestType.Shared, true, false, false, false, false, false); + managementOffering.setState(NetworkOffering.State.Enabled); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(managementOffering); + + Assert.assertNull(prepare()); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsDisabledPhysicalNetwork() { + physicalNetwork.setState(com.cloud.network.PhysicalNetwork.State.Disabled); + + prepare(); + } + + @Test + public void normalizesBasicZoneDefaults() { + zone.setNetworkType(NetworkType.Basic); + offering = newOffering(GuestType.Shared, false); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(networkModel.areServicesSupportedByNetworkOffering(OFFERING_ID, Service.SourceNat)).thenReturn(false); + when(vlanValidationService.encodeVlanIdIntoBroadcastUri(eq(Vlan.UNTAGGED), eq(physicalNetwork))).thenReturn(URI.create("vlan://untagged")); + + GuestNetworkCreationPreparation preparation = prepare(null, null, ACLType.Domain, null, null, null, null, null, null, null, null, null, null, null, null, null, + false); + + Assert.assertTrue(preparation.getSubdomainAccess()); + Assert.assertEquals(BroadcastDomainType.Native, preparation.getPredefinedNetwork().getBroadcastDomainType()); + verify(vlanValidationService).validateGuestNetworkOfferingVlan(eq(Vlan.UNTAGGED), eq(null), anyBoolean(), eq(offering), eq(physicalNetwork), eq(zone), eq(ZONE_ID), + eq(owner), eq(false)); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsBasicZoneNonDomainAcl() { + zone.setNetworkType(NetworkType.Basic); + prepare(null, null, ACLType.Account, null, null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsBasicZoneWithExistingGuestNetwork() { + zone.setNetworkType(NetworkType.Basic); + when(networksDao.listByZoneAndTrafficType(ZONE_ID, TrafficType.Guest)).thenReturn(Collections.singletonList(new NetworkVO())); + + prepare(null, null, ACLType.Domain, null, null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsBasicZoneNonRootDomain() { + zone.setNetworkType(NetworkType.Basic); + prepare(null, 2L, ACLType.Domain, null, null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsBasicZoneTaggedVlan() { + zone.setNetworkType(NetworkType.Basic); + prepare("123", null, ACLType.Domain, null, null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsBasicZoneCidr() { + zone.setNetworkType(NetworkType.Basic); + prepare(null, null, ACLType.Domain, null, "10.0.0.0/24", null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsAdvancedSecurityGroupIsolatedPvlan() { + zone.setSecurityGroupEnabled(true); + prepare("123", null, ACLType.Account, null, null, "456", null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsAdvancedSecurityGroupNonSharedOrL2Offering() { + zone.setSecurityGroupEnabled(true); + offering = newOffering(GuestType.Isolated, true); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + + prepare(); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsAdvancedSecurityGroupSourceNat() { + zone.setSecurityGroupEnabled(true); + when(networkModel.areServicesSupportedByNetworkOffering(anyLong(), eq(Service.SourceNat))).thenReturn(true); + + prepare(); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsCustomNetworkDomainWhenSuffixModificationUnsupported() { + when(networkModel.areServicesSupportedByNetworkOffering(OFFERING_ID, Service.Dns)).thenReturn(true); + when(networkModel.getNetworkOfferingServiceCapabilities(offering, Service.Dns)).thenReturn(Collections.emptyMap()); + + prepare("123", null, ACLType.Account, "custom.example.com", null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test + public void generatesDefaultNetworkDomainWhenSuffixModificationSupported() { + when(networkModel.areServicesSupportedByNetworkOffering(OFFERING_ID, Service.Dns)).thenReturn(true); + when(networkModel.getNetworkOfferingServiceCapabilities(offering, Service.Dns)).thenReturn(dnsCapabilities(true)); + when(networkModel.getAccountNetworkDomain(ACCOUNT_ID, ZONE_ID)).thenReturn(null); + + GuestNetworkCreationPreparation preparation = prepare("123", null, ACLType.Account, null, "10.0.0.0/24", null, null, "10.0.0.1", null, null, null, null, + null, null, null, null, false); + + Assert.assertEquals("cs28cloud.internal", preparation.getNetworkDomain()); + Assert.assertEquals("cs28cloud.internal", preparation.getPredefinedNetwork().getNetworkDomain()); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsInvalidCustomNetworkDomain() { + when(networkModel.areServicesSupportedByNetworkOffering(OFFERING_ID, Service.Dns)).thenReturn(true); + when(networkModel.getNetworkOfferingServiceCapabilities(offering, Service.Dns)).thenReturn(dnsCapabilities(true)); + + prepare("123", null, ACLType.Account, "-bad.example.com", null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void requiresCidrForAdvancedSharedNetwork() { + prepare("123", null, ACLType.Account, null, null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void requiresCidrForAdvancedIsolatedWithoutSourceNatOrGateway() { + offering = newOffering(GuestType.Isolated, true); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(networkModel.areServicesSupportedByNetworkOffering(anyLong(), eq(Service.SourceNat))).thenReturn(false); + when(networkModel.areServicesSupportedByNetworkOffering(anyLong(), eq(Service.Gateway))).thenReturn(false); + + prepare("123", null, ACLType.Account, null, null, null, null, null, null, null, null, null, null, null, null, null, false); + } + + @Test + public void buildsPredefinedNetworkWithAddressingDnsMtuBroadcastAndRouterFields() { + when(vlanValidationService.encodeVlanIdIntoBroadcastUri(eq("123"), eq(physicalNetwork))).thenReturn(URI.create("vlan://123")); + + GuestNetworkCreationPreparation preparation = prepare("123", null, ACLType.Account, "custom.example.com", "10.0.0.0/24", null, null, "10.0.0.1", "2001:db8::1", + "2001:db8::/64", "ext-1", "169.254.1.1", "fe80::1", "1.1.1.1", "1.0.0.1", "2606:4700:4700::1111", "2606:4700:4700::1001", false); + + NetworkVO network = preparation.getPredefinedNetwork(); + Assert.assertEquals("10.0.0.0/24", network.getCidr()); + Assert.assertEquals("10.0.0.1", network.getGateway()); + Assert.assertEquals("2001:db8::/64", network.getIp6Cidr()); + Assert.assertEquals("2001:db8::1", network.getIp6Gateway()); + Assert.assertEquals("ext-1", network.getExternalId()); + Assert.assertEquals("169.254.1.1", network.getRouterIp()); + Assert.assertEquals("fe80::1", network.getRouterIpv6()); + Assert.assertEquals("1.1.1.1", network.getDns1()); + Assert.assertEquals("1.0.0.1", network.getDns2()); + Assert.assertEquals("2606:4700:4700::1111", network.getIp6Dns1()); + Assert.assertEquals("2606:4700:4700::1001", network.getIp6Dns2()); + Assert.assertEquals(Integer.valueOf(1400), network.getPublicMtu()); + Assert.assertEquals(Integer.valueOf(NetworkService.VRPrivateInterfaceMtu.defaultValue()), network.getPrivateMtu()); + Assert.assertEquals(URI.create("vlan://123"), network.getBroadcastUri()); + Assert.assertEquals(BroadcastDomainType.Vlan, network.getBroadcastDomainType()); + Assert.assertEquals(Integer.valueOf(24), network.getNetworkCidrSize()); + Assert.assertFalse(network.getKeepMacAddressOnPublicNic()); + Assert.assertEquals(PHYSICAL_NETWORK_ID, preparation.getPlan().getPhysicalNetworkId().longValue()); + } + + @Test(expected = InvalidParameterValueException.class) + public void rejectsDuplicatePvlan() { + when(networksDao.listByPhysicalNetworkPvlan(PHYSICAL_NETWORK_ID, "pvlan://123-i456", PVlanType.Isolated)).thenReturn(Collections.singletonList(new NetworkVO())); + + prepare("123", null, ACLType.Account, null, "10.0.0.0/24", "456", PVlanType.Isolated, "10.0.0.1", null, null, null, null, null, null, null, null, false); + } + + private GuestNetworkCreationPreparation prepare() { + return prepare("123", null, ACLType.Account, null, "10.0.0.0/24", null, null, "10.0.0.1", null, null, null, null, null, null, null, null, true); + } + + private GuestNetworkCreationPreparation prepare(String vlanId, Long domainId, ACLType aclType, String networkDomain, String cidr, String isolatedPvlan, + PVlanType isolatedPvlanType, String gateway, String ip6Gateway, String ip6Cidr, String externalId, String routerIp, String routerIpv6, String ip4Dns1, + String ip4Dns2, String ip6Dns1, boolean keepMacAddressOnPublicNic) { + return prepare(vlanId, domainId, aclType, networkDomain, cidr, isolatedPvlan, isolatedPvlanType, gateway, ip6Gateway, ip6Cidr, externalId, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, null, keepMacAddressOnPublicNic); + } + + private GuestNetworkCreationPreparation prepare(String vlanId, Long domainId, ACLType aclType, String networkDomain, String cidr, String isolatedPvlan, + PVlanType isolatedPvlanType, String gateway, String ip6Gateway, String ip6Cidr, String externalId, String routerIp, String routerIpv6, String ip4Dns1, + String ip4Dns2, String ip6Dns1, String ip6Dns2, boolean keepMacAddressOnPublicNic) { + return service.prepareGuestNetworkCreation(OFFERING_ID, gateway, cidr, vlanId, false, networkDomain, owner, domainId == null ? Domain.ROOT_DOMAIN : domainId, + physicalNetwork, ZONE_ID, aclType, null, ip6Gateway, ip6Cidr, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, ip4Dns1, ip4Dns2, + ip6Dns1, ip6Dns2, new Pair<>(1400, 0), 24, keepMacAddressOnPublicNic); + } + + private NetworkOfferingVO newOffering(GuestType guestType, boolean specifyVlan) { + NetworkOfferingVO result = new NetworkOfferingVO("offering", "offering", TrafficType.Guest, false, specifyVlan, 0, 0, false, Availability.Optional, null, + guestType, true, false, false, false, false, false); + result.setState(NetworkOffering.State.Enabled); + return result; + } + + private Map dnsCapabilities(boolean allowSuffixModification) { + Map capabilities = new HashMap<>(); + capabilities.put(Capability.AllowDnsSuffixModification, Boolean.toString(allowSuffixModification)); + return capabilities; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupServiceImplTest.java new file mode 100644 index 000000000000..65338d7b75b2 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkHostSetupServiceImplTest.java @@ -0,0 +1,269 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.CheckNetworkAnswer; +import com.cloud.agent.api.CheckNetworkCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.alert.AlertManager; +import com.cloud.dc.DataCenter; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.ConnectionException; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.PhysicalNetwork; +import com.cloud.network.PhysicalNetworkSetupInfo; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao; +import com.cloud.network.dao.PhysicalNetworkTrafficTypeVO; +import com.cloud.network.dao.PhysicalNetworkVO; + +public class NetworkHostSetupServiceImplTest { + + private static final long HOST_ID = 101L; + private static final long DC_ID = 11L; + private static final long POD_ID = 22L; + private static final long PHYSICAL_NETWORK_ID = 33L; + private static final String PRIVATE_IP_ADDRESS = "10.1.1.5"; + + private NetworkHostSetupServiceImpl service; + private DataCenterDao dataCenterDao; + private PhysicalNetworkDao physicalNetworkDao; + private PhysicalNetworkTrafficTypeDao physicalNetworkTrafficTypeDao; + private AgentManager agentManager; + private AlertManager alertManager; + private Host host; + private StartupRoutingCommand startup; + + @Before + public void setUp() { + service = new NetworkHostSetupServiceImpl(); + dataCenterDao = mock(DataCenterDao.class); + physicalNetworkDao = mock(PhysicalNetworkDao.class); + physicalNetworkTrafficTypeDao = mock(PhysicalNetworkTrafficTypeDao.class); + agentManager = mock(AgentManager.class); + alertManager = mock(AlertManager.class); + service.dataCenterDao = dataCenterDao; + service.physicalNetworkDao = physicalNetworkDao; + service.physicalNetworkTrafficTypeDao = physicalNetworkTrafficTypeDao; + service.agentManager = agentManager; + service.alertManager = alertManager; + + host = mock(Host.class); + when(host.getId()).thenReturn(HOST_ID); + when(host.getPodId()).thenReturn(POD_ID); + + startup = mock(StartupRoutingCommand.class); + when(startup.getDataCenter()).thenReturn("zone-a"); + when(startup.getPrivateIpAddress()).thenReturn(PRIVATE_IP_ADDRESS); + when(startup.getHypervisorType()).thenReturn(HypervisorType.KVM); + } + + @Test + public void testProcessConnectReturnsForNonRoutingStartupCommand() throws ConnectionException { + service.processConnect(host, mock(StartupCommand.class), false); + + verify(dataCenterDao, never()).findByName(anyString()); + verify(physicalNetworkDao, never()).listByZone(anyLong()); + verify(agentManager, never()).easySend(anyLong(), any()); + } + + @Test + public void testProcessConnectReturnsForTransferredConnection() throws ConnectionException { + when(startup.isConnectionTransferred()).thenReturn(true); + + service.processConnect(host, startup, false); + + verify(dataCenterDao, never()).findByName(anyString()); + verify(physicalNetworkDao, never()).listByZone(anyLong()); + verify(agentManager, never()).easySend(anyLong(), any()); + } + + @Test + public void testProcessConnectFindsDataCenterByName() throws ConnectionException { + DataCenterVO dataCenter = dataCenter(); + when(dataCenterDao.findByName("zone-a")).thenReturn(dataCenter); + stubSuccessfulEmptyNetworkCheck(); + + service.processConnect(host, startup, false); + + verify(dataCenterDao).findByName("zone-a"); + verify(dataCenterDao, never()).findById(anyLong()); + } + + @Test + public void testProcessConnectFindsDataCenterByNumericIdWhenNameMissing() throws ConnectionException { + DataCenterVO dataCenter = dataCenter(); + when(startup.getDataCenter()).thenReturn(String.valueOf(DC_ID)); + when(dataCenterDao.findByName(String.valueOf(DC_ID))).thenReturn(null); + when(dataCenterDao.findById(DC_ID)).thenReturn(dataCenter); + stubSuccessfulEmptyNetworkCheck(); + + service.processConnect(host, startup, false); + + verify(dataCenterDao).findByName(String.valueOf(DC_ID)); + verify(dataCenterDao).findById(DC_ID); + } + + @Test + public void testProcessConnectThrowsIllegalArgumentWhenDataCenterMissing() { + when(startup.getDataCenter()).thenReturn("missing-zone"); + when(dataCenterDao.findByName("missing-zone")).thenReturn(null); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> service.processConnect(host, startup, false)); + + assertTrue(exception.getMessage().contains("Host " + PRIVATE_IP_ADDRESS + " sent incorrect data center: missing-zone")); + } + + @Test + public void testProcessConnectBuildsNetworkInfoAndSendsCheckCommand() throws ConnectionException { + stubDataCenterByName(); + stubPhysicalNetworkSetup(); + ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(CheckNetworkCommand.class); + when(agentManager.easySend(eq(Long.valueOf(HOST_ID)), any(CheckNetworkCommand.class))).thenAnswer(invocation -> { + CheckNetworkCommand command = invocation.getArgument(1); + return new CheckNetworkAnswer(command, true, "ok"); + }); + + service.processConnect(host, startup, false); + + verify(agentManager).easySend(eq(Long.valueOf(HOST_ID)), commandCaptor.capture()); + List networkInfo = commandCaptor.getValue().getPhysicalNetworkInfoList(); + assertEquals(1, networkInfo.size()); + PhysicalNetworkSetupInfo setupInfo = networkInfo.get(0); + assertEquals(Long.valueOf(PHYSICAL_NETWORK_ID), setupInfo.getPhysicalNetworkId()); + assertEquals("cloudbr0", setupInfo.getPublicNetworkName()); + assertEquals("cloudbr1", setupInfo.getPrivateNetworkName()); + assertEquals("cloudbr2", setupInfo.getGuestNetworkName()); + assertEquals("cloudbr3", setupInfo.getStorageNetworkName()); + assertEquals("100", setupInfo.getMgmtVlan()); + } + + @Test + public void testProcessConnectSuccessWithoutReconnectDoesNotAlert() throws ConnectionException { + stubDataCenterByName(); + stubPhysicalNetworkSetup(); + stubSuccessfulAgentAnswer(false); + + service.processConnect(host, startup, false); + + verify(alertManager, never()).sendAlert(any(), anyLong(), anyLong(), anyString(), anyString()); + } + + @Test + public void testProcessConnectNullAnswerThrowsSetupErrorConnectionException() { + stubDataCenterByName(); + stubPhysicalNetworkSetup(); + when(agentManager.easySend(eq(Long.valueOf(HOST_ID)), any(CheckNetworkCommand.class))).thenReturn(null); + + ConnectionException exception = assertThrows(ConnectionException.class, () -> service.processConnect(host, startup, false)); + + assertTrue(exception.isSetupError()); + assertTrue(exception.getMessage().contains("Unable to get an answer to the CheckNetworkCommand from agent")); + } + + @Test + public void testProcessConnectFailedAnswerAlertsAndThrowsSetupError() { + stubDataCenterByName(); + stubPhysicalNetworkSetup(); + String msg = "Incorrect Network setup on agent, Reinitialize agent after network names are setup, details : bad bridge"; + when(agentManager.easySend(eq(Long.valueOf(HOST_ID)), any(CheckNetworkCommand.class))).thenAnswer(invocation -> { + CheckNetworkCommand command = invocation.getArgument(1); + return new CheckNetworkAnswer(command, false, "bad bridge"); + }); + + ConnectionException exception = assertThrows(ConnectionException.class, () -> service.processConnect(host, startup, false)); + + verify(alertManager).sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, DC_ID, POD_ID, msg, msg); + assertTrue(exception.isSetupError()); + assertEquals(msg, exception.getMessage()); + } + + @Test + public void testProcessConnectReconnectAnswerThrowsNonSetupError() { + stubDataCenterByName(); + stubPhysicalNetworkSetup(); + stubSuccessfulAgentAnswer(true); + + ConnectionException exception = assertThrows(ConnectionException.class, () -> service.processConnect(host, startup, false)); + + assertFalse(exception.isSetupError()); + assertEquals("Reinitialize agent after network setup.", exception.getMessage()); + } + + private void stubDataCenterByName() { + when(dataCenterDao.findByName("zone-a")).thenReturn(dataCenter()); + } + + private void stubSuccessfulEmptyNetworkCheck() { + when(physicalNetworkDao.listByZone(DC_ID)).thenReturn(Collections.emptyList()); + stubSuccessfulAgentAnswer(false); + } + + private void stubSuccessfulAgentAnswer(boolean reconnect) { + when(agentManager.easySend(eq(Long.valueOf(HOST_ID)), any(CheckNetworkCommand.class))).thenAnswer(invocation -> { + CheckNetworkCommand command = invocation.getArgument(1); + return new CheckNetworkAnswer(command, true, "ok", reconnect); + }); + } + + private void stubPhysicalNetworkSetup() { + when(physicalNetworkDao.listByZone(DC_ID)).thenReturn(Collections.singletonList(physicalNetwork())); + when(physicalNetworkTrafficTypeDao.getNetworkTag(PHYSICAL_NETWORK_ID, TrafficType.Public, HypervisorType.KVM)).thenReturn("cloudbr0"); + when(physicalNetworkTrafficTypeDao.getNetworkTag(PHYSICAL_NETWORK_ID, TrafficType.Management, HypervisorType.KVM)).thenReturn("cloudbr1"); + when(physicalNetworkTrafficTypeDao.getNetworkTag(PHYSICAL_NETWORK_ID, TrafficType.Guest, HypervisorType.KVM)).thenReturn("cloudbr2"); + when(physicalNetworkTrafficTypeDao.getNetworkTag(PHYSICAL_NETWORK_ID, TrafficType.Storage, HypervisorType.KVM)).thenReturn("cloudbr3"); + when(physicalNetworkTrafficTypeDao.findBy(PHYSICAL_NETWORK_ID, TrafficType.Management)).thenReturn(managementTraffic()); + } + + private DataCenterVO dataCenter() { + return new DataCenterVO(DC_ID, "zone-a", "zone-a", "8.8.8.8", "8.8.4.4", "10.0.0.1", "10.0.0.2", "10.1.0.0/16", "domain", + 1L, DataCenter.NetworkType.Advanced, "token", "example.com"); + } + + private PhysicalNetworkVO physicalNetwork() { + return new PhysicalNetworkVO(PHYSICAL_NETWORK_ID, DC_ID, null, null, null, PhysicalNetwork.BroadcastDomainRange.ZONE, "physical-network"); + } + + private PhysicalNetworkTrafficTypeVO managementTraffic() { + return new PhysicalNetworkTrafficTypeVO(PHYSICAL_NETWORK_ID, TrafficType.Management, null, "cloudbr1", null, null, "100", null, null); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationServiceImplTest.java new file mode 100644 index 000000000000..5248ae07a9c7 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOfferingVlanValidationServiceImplTest.java @@ -0,0 +1,276 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.DataCenterVnetVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DataCenterVnetDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.AccountGuestVlanMapDao; +import com.cloud.network.dao.AccountGuestVlanMapVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.exception.CloudRuntimeException; + +public class NetworkOfferingVlanValidationServiceImplTest { + + private static final long ZONE_ID = 11L; + private static final long PHYSICAL_NETWORK_ID = 22L; + private static final long OWNER_ID = 33L; + private static final String VLAN_ID = "123"; + + private NetworkOfferingVlanValidationServiceImpl service; + private DataCenterDao dataCenterDao; + private NetworkDao networksDao; + private DataCenterVnetDao dataCenterVnetDao; + private AccountGuestVlanMapDao accountGuestVlanMapDao; + private NetworkOfferingDao networkOfferingDao; + private NetworkModel networkModel; + private NetworkOfferingVO offering; + private PhysicalNetworkVO physicalNetwork; + private DataCenterVO zone; + private Account owner; + + @Before + public void setUp() { + dataCenterDao = mock(DataCenterDao.class); + networksDao = mock(NetworkDao.class); + dataCenterVnetDao = mock(DataCenterVnetDao.class); + accountGuestVlanMapDao = mock(AccountGuestVlanMapDao.class); + networkOfferingDao = mock(NetworkOfferingDao.class); + networkModel = mock(NetworkModel.class); + + service = new NetworkOfferingVlanValidationServiceImpl(); + service.dataCenterDao = dataCenterDao; + service.networksDao = networksDao; + service.dataCenterVnetDao = dataCenterVnetDao; + service.accountGuestVlanMapDao = accountGuestVlanMapDao; + service.networkOfferingDao = networkOfferingDao; + service.networkModel = networkModel; + + offering = mock(NetworkOfferingVO.class); + when(offering.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + when(offering.isSpecifyVlan()).thenReturn(true); + when(offering.getId()).thenReturn(44L); + + NetworkOfferingVO privateGatewayOffering = mock(NetworkOfferingVO.class); + when(privateGatewayOffering.getId()).thenReturn(99L); + when(networkOfferingDao.findByUniqueName(NetworkOffering.SystemPrivateGatewayNetworkOfferingWithoutVlan)).thenReturn(privateGatewayOffering); + + physicalNetwork = new PhysicalNetworkVO(PHYSICAL_NETWORK_ID, ZONE_ID, null, null, null, null, "physical-network"); + physicalNetwork.setIsolationMethods(new ArrayList<>(Collections.singletonList("vlan"))); + + zone = mock(DataCenterVO.class); + when(zone.getName()).thenReturn("zone1"); + + owner = mock(Account.class); + when(owner.getAccountId()).thenReturn(OWNER_ID); + when(owner.getAccountName()).thenReturn("owner"); + + when(dataCenterDao.findVnet(anyLong(), anyLong(), anyString())).thenReturn(Collections.emptyList()); + when(networksDao.listByZoneAndUriAndGuestType(anyLong(), anyString(), org.mockito.ArgumentMatchers.isNull())).thenReturn(Collections.emptyList()); + when(networksDao.listByZoneAndUriAndGuestType(anyLong(), anyString(), org.mockito.ArgumentMatchers.eq(GuestType.Isolated))).thenReturn(Collections.emptyList()); + when(dataCenterVnetDao.findVnet(anyLong(), anyString())).thenReturn(Collections.emptyList()); + when(accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(anyLong())).thenReturn(Collections.emptyList()); + } + + @Test + public void isSharedNetworkWithoutSpecifyVlanRequiresGuestSharedAndNoSpecifiedVlan() { + Assert.assertFalse(service.isSharedNetworkWithoutSpecifyVlan(null)); + + NetworkOfferingVO nonGuest = mock(NetworkOfferingVO.class); + when(nonGuest.getTrafficType()).thenReturn(TrafficType.Management); + Assert.assertFalse(service.isSharedNetworkWithoutSpecifyVlan(nonGuest)); + + when(offering.getGuestType()).thenReturn(GuestType.Isolated); + when(offering.isSpecifyVlan()).thenReturn(false); + Assert.assertFalse(service.isSharedNetworkWithoutSpecifyVlan(offering)); + + when(offering.getGuestType()).thenReturn(GuestType.Shared); + Assert.assertTrue(service.isSharedNetworkWithoutSpecifyVlan(offering)); + } + + @Test + public void encodeVlanIdIntoBroadcastUriUsesVxlanIsolationMethod() { + physicalNetwork.setIsolationMethods(new ArrayList<>(Collections.singletonList("VXLAN"))); + + URI uri = service.encodeVlanIdIntoBroadcastUri(VLAN_ID, physicalNetwork); + + Assert.assertEquals("vxlan", uri.getScheme()); + Assert.assertEquals("vxlan://123", uri.toString()); + } + + @Test + public void encodeVlanIdIntoBroadcastUriFallsBackToVlanUri() { + URI uri = service.encodeVlanIdIntoBroadcastUri(VLAN_ID, physicalNetwork); + + Assert.assertEquals("vlan", uri.getScheme()); + Assert.assertEquals("vlan://123", uri.toString()); + } + + @Test(expected = InvalidParameterValueException.class) + public void encodeVlanIdIntoBroadcastUriRejectsNullPhysicalNetwork() { + service.encodeVlanIdIntoBroadcastUri(VLAN_ID, null); + } + + @Test(expected = CloudRuntimeException.class) + public void encodeVlanIdIntoBroadcastUriPreservesBlankVlanFailure() { + service.encodeVlanIdIntoBroadcastUri(" ", physicalNetwork); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsSpecifiedVlanWhenOfferingDoesNotAllowIt() { + when(offering.isSpecifyVlan()).thenReturn(false); + + validate(VLAN_ID, null, false, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsMissingVlanWhenOfferingRequiresIt() { + validate(null, null, false, false); + } + + @Test + public void validateAllowsSharedOfferingWithoutSpecifyVlanAndSkipsDynamicAllocationConflict() { + when(offering.isSpecifyVlan()).thenReturn(false); + when(offering.getGuestType()).thenReturn(GuestType.Shared); + when(dataCenterDao.findVnet(ZONE_ID, PHYSICAL_NETWORK_ID, "123")).thenReturn(Collections.singletonList(new DataCenterVnetVO("123", ZONE_ID, PHYSICAL_NETWORK_ID))); + + validate(VLAN_ID, null, false, false); + + verify(networksDao, never()).listByZoneAndUriAndGuestType(ZONE_ID, "vlan://123", null); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsPrimaryDynamicAllocationConflictForNonBypassOffering() { + when(dataCenterDao.findVnet(ZONE_ID, PHYSICAL_NETWORK_ID, "123")).thenReturn(Collections.singletonList(new DataCenterVnetVO("123", ZONE_ID, PHYSICAL_NETWORK_ID))); + + validate(VLAN_ID, null, false, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsSecondaryPvlanDynamicAllocationConflict() { + when(dataCenterDao.findVnet(ZONE_ID, PHYSICAL_NETWORK_ID, "456")).thenReturn(Collections.singletonList(new DataCenterVnetVO("456", ZONE_ID, PHYSICAL_NETWORK_ID))); + + validate(VLAN_ID, "456", false, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsExistingPrimaryNetworkUriOverlap() { + when(networksDao.listByZoneAndUriAndGuestType(ZONE_ID, "vlan://123", null)).thenReturn(Collections.singletonList(mock(NetworkVO.class))); + + validate(VLAN_ID, null, false, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsExistingSecondaryNetworkUriOverlap() { + when(networksDao.listByZoneAndUriAndGuestType(ZONE_ID, "vlan://456", null)).thenReturn(Collections.singletonList(mock(NetworkVO.class))); + + validate(VLAN_ID, "456", false, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsDedicatedVlanMappedToDifferentAccount() { + DataCenterVnetVO vnet = new DataCenterVnetVO("123", ZONE_ID, PHYSICAL_NETWORK_ID); + vnet.setAccountGuestVlanMapId(55L); + AccountGuestVlanMapVO map = mock(AccountGuestVlanMapVO.class); + when(map.getAccountId()).thenReturn(OWNER_ID + 1); + when(dataCenterVnetDao.findVnet(ZONE_ID, "123")).thenReturn(Collections.singletonList(vnet)); + when(accountGuestVlanMapDao.findById(55L)).thenReturn(map); + + validate(VLAN_ID, null, false, false); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateRejectsSystemPoolVlanWhenOwnerStillHasUnusedDedicatedRange() { + when(dataCenterVnetDao.findVnet(ZONE_ID, "123")).thenReturn(Collections.singletonList(new DataCenterVnetVO("123", ZONE_ID, PHYSICAL_NETWORK_ID))); + when(accountGuestVlanMapDao.listAccountGuestVlanMapsByAccount(OWNER_ID)).thenReturn(Collections.singletonList(new AccountGuestVlanMapVO(OWNER_ID, PHYSICAL_NETWORK_ID))); + when(dataCenterVnetDao.countVnetsAllocatedToAccount(ZONE_ID, OWNER_ID)).thenReturn(0); + when(dataCenterVnetDao.countVnetsDedicatedToAccount(ZONE_ID, OWNER_ID)).thenReturn(1); + + validate(VLAN_ID, null, false, false); + } + + @Test + public void validateAllowsUuidVlanWithoutExistingNetworkOrDedicatedRangeChecks() { + String uuidVlan = "7ee3f1f0-b527-4b8b-85bb-232fc7601b6d"; + physicalNetwork.setIsolationMethods(new ArrayList<>(Collections.singletonList("vxlan"))); + + validate(uuidVlan, null, false, false); + + verify(networksDao, never()).listByZoneAndUriAndGuestType(anyLong(), anyString(), org.mockito.ArgumentMatchers.isNull()); + verify(dataCenterVnetDao, never()).findVnet(anyLong(), anyString()); + } + + @Test(expected = InvalidParameterValueException.class) + public void checkL2OfferingServicesRejectsMultipleServices() { + when(offering.getGuestType()).thenReturn(GuestType.L2); + when(networkModel.listNetworkOfferingServices(offering.getId())).thenReturn(Arrays.asList(Service.UserData, Service.Dhcp)); + when(networkModel.areServicesSupportedByNetworkOffering(offering.getId(), Service.UserData)).thenReturn(true); + + service.checkL2OfferingServices(offering); + } + + @Test + public void checkL2OfferingServicesAllowsUserDataOnly() { + when(offering.getGuestType()).thenReturn(GuestType.L2); + when(networkModel.listNetworkOfferingServices(offering.getId())).thenReturn(Collections.singletonList(Service.UserData)); + when(networkModel.areServicesSupportedByNetworkOffering(offering.getId(), Service.UserData)).thenReturn(true); + + service.checkL2OfferingServices(offering); + } + + private void validate(String vlanId, String isolatedPvlan, boolean bypassVlanOverlapCheck, boolean isPrivateNetwork) { + service.validateGuestNetworkOfferingVlan( + vlanId, + isolatedPvlan, + bypassVlanOverlapCheck, + offering, + physicalNetwork, + zone, + ZONE_ID, + owner, + isPrivateNetwork); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java index e3989737112d..47f5be0268c6 100644 --- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java @@ -17,7 +17,6 @@ package org.apache.cloudstack.engine.orchestration; import static org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService.NetworkLockTimeout; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -27,44 +26,46 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import com.cloud.dc.DataCenter; -import com.cloud.exception.InsufficientVirtualNetworkCapacityException; import com.cloud.network.IpAddressManager; -import com.cloud.utils.Pair; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import org.mockito.ArgumentMatchers; -import org.mockito.MockedStatic; import org.mockito.Mockito; -import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.to.NicTO; import com.cloud.dc.Vlan; import com.cloud.dc.VlanVO; import com.cloud.dc.dao.VlanDao; import com.cloud.deploy.DeployDestination; +import com.cloud.exception.ConnectionException; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor; import com.cloud.network.IpAddress.State; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; import com.cloud.network.Network.Service; import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkProfile; import com.cloud.network.Networks.TrafficType; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.network.dao.RouterNetworkDao; import com.cloud.network.element.DhcpServiceProvider; @@ -73,9 +74,10 @@ import com.cloud.network.vpc.VpcManager; import com.cloud.network.vpc.VpcVO; import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.user.Account; +import com.cloud.utils.Pair; import com.cloud.utils.db.EntityManager; -import com.cloud.utils.db.Transaction; -import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.Ip; import com.cloud.vm.DomainRouterVO; @@ -101,6 +103,8 @@ public class NetworkOrchestratorTest extends TestCase { NetworkOrchestrator testOrchestrator = Mockito.spy(new NetworkOrchestrator()); + RequestedNicIpReservationServiceImpl requestedNicIpReservationService; + NetworkOfferingVlanValidationServiceImpl networkOfferingVlanValidationService; private String guruName = "GuestNetworkGuru"; private String dhcpProvider = "VirtualRouter"; @@ -124,6 +128,8 @@ public void setUp() { testOrchestrator._networkModel = mock(NetworkModel.class); testOrchestrator._nicSecondaryIpDao = mock(NicSecondaryIpDao.class); testOrchestrator._ntwkSrvcDao = mock(NetworkServiceMapDao.class); + testOrchestrator._ntwkOfferingSrvcDao = mock(NetworkOfferingServiceMapDao.class); + testOrchestrator._pNSPDao = mock(PhysicalNetworkServiceProviderDao.class); testOrchestrator._nicIpAliasDao = mock(NicIpAliasDao.class); testOrchestrator._ipAddressDao = mock(IPAddressDao.class); testOrchestrator._vlanDao = mock(VlanDao.class); @@ -132,9 +138,97 @@ public void setUp() { testOrchestrator.routerDao = mock(DomainRouterDao.class); testOrchestrator.routerNetworkDao = mock(RouterNetworkDao.class); testOrchestrator._vpcMgr = mock(VpcManager.class); - testOrchestrator.routerJoinDao = mock(DomainRouterJoinDao.class); testOrchestrator._ipAddrMgr = mock(IpAddressManager.class); testOrchestrator._entityMgr = mock(EntityManager.class); + testOrchestrator._networkOfferingDao = mock(com.cloud.offerings.dao.NetworkOfferingDao.class); + testOrchestrator._dcDao = mock(com.cloud.dc.dao.DataCenterDao.class); + testOrchestrator._datacenterVnetDao = mock(com.cloud.dc.dao.DataCenterVnetDao.class); + testOrchestrator._accountGuestVlanMapDao = mock(com.cloud.network.dao.AccountGuestVlanMapDao.class); + + requestedNicIpReservationService = Mockito.spy(new RequestedNicIpReservationServiceImpl()); + requestedNicIpReservationService.vlanDao = testOrchestrator._vlanDao; + requestedNicIpReservationService.ipAddressDao = testOrchestrator._ipAddressDao; + requestedNicIpReservationService.networkModel = testOrchestrator._networkModel; + testOrchestrator.requestedNicIpReservationService = requestedNicIpReservationService; + + networkOfferingVlanValidationService = Mockito.spy(new NetworkOfferingVlanValidationServiceImpl()); + networkOfferingVlanValidationService.dataCenterDao = testOrchestrator._dcDao; + networkOfferingVlanValidationService.networksDao = testOrchestrator._networksDao; + networkOfferingVlanValidationService.dataCenterVnetDao = testOrchestrator._datacenterVnetDao; + networkOfferingVlanValidationService.accountGuestVlanMapDao = testOrchestrator._accountGuestVlanMapDao; + networkOfferingVlanValidationService.networkOfferingDao = testOrchestrator._networkOfferingDao; + networkOfferingVlanValidationService.networkModel = testOrchestrator._networkModel; + testOrchestrator.networkOfferingVlanValidationService = networkOfferingVlanValidationService; + + // Wire a real NetworkProviderResolutionServiceImpl that shares the + // same dao/network-model mocks as the orchestrator under test, so + // existing `verify(orchestrator._ntwkSrvcDao, ...)` and + // `verify(orchestrator._networkModel, ...)` assertions in tests that + // indirectly exercise getDhcpServiceProvider / getDnsServiceProvider + // through removeNic et al. continue to observe the calls. + NetworkProviderResolutionServiceImpl resolutionService = new NetworkProviderResolutionServiceImpl(); + resolutionService.networkServiceMapDao = testOrchestrator._ntwkSrvcDao; + resolutionService.networkModel = testOrchestrator._networkModel; + resolutionService.entityManager = testOrchestrator._entityMgr; + testOrchestrator.networkProviderResolutionService = resolutionService; + + NetworkProviderMappingServiceImpl mappingService = new NetworkProviderMappingServiceImpl(); + mappingService.networkOfferingServiceMapDao = testOrchestrator._ntwkOfferingSrvcDao; + mappingService.networkModel = testOrchestrator._networkModel; + mappingService.physicalNetworkServiceProviderDao = testOrchestrator._pNSPDao; + mappingService.networkServiceMapDao = testOrchestrator._ntwkSrvcDao; + testOrchestrator.networkProviderMappingService = mappingService; + + // Wire a real NicDhcpCleanupServiceImpl sharing the same mocks so that + // assertions on _ntwkSrvcDao / _networkModel / _nicDao made by tests + // that exercise removeNic -> cleanupNicDhcpDnsEntry still work. + NicDhcpCleanupServiceImpl dhcpCleanupService = new NicDhcpCleanupServiceImpl(); + dhcpCleanupService.networkServiceMapDao = testOrchestrator._ntwkSrvcDao; + dhcpCleanupService.networkModel = testOrchestrator._networkModel; + dhcpCleanupService.networksDao = testOrchestrator._networksDao; + dhcpCleanupService.nicDao = testOrchestrator._nicDao; + dhcpCleanupService.nicIpAliasDao = testOrchestrator._nicIpAliasDao; + dhcpCleanupService.publicIpAddressDao = testOrchestrator._publicIpAddressDao; + dhcpCleanupService.networkProviderResolutionService = resolutionService; + dhcpCleanupService.networkElements = new ArrayList<>(); + testOrchestrator.nicDhcpCleanupService = dhcpCleanupService; + + NicAuxiliaryServiceImpl nicAuxiliaryService = new NicAuxiliaryServiceImpl(); + nicAuxiliaryService.nicDao = testOrchestrator._nicDao; + nicAuxiliaryService.nicSecondaryIpDao = testOrchestrator._nicSecondaryIpDao; + nicAuxiliaryService.networksDao = testOrchestrator._networksDao; + nicAuxiliaryService.networkModel = testOrchestrator._networkModel; + testOrchestrator.nicAuxiliaryService = nicAuxiliaryService; + + NicElementPreparationServiceImpl elementPreparationService = new NicElementPreparationServiceImpl(); + elementPreparationService.networkModel = testOrchestrator._networkModel; + elementPreparationService.nicDhcpCleanupService = dhcpCleanupService; + testOrchestrator.nicElementPreparationService = elementPreparationService; + + // Wire a real NicProfileMtuServiceImpl backed by mocks. No tests trigger + // the MTU code path directly, but the orchestrator-level call sites in + // allocateNic/prepareNic/importNic delegate to it. + NicProfileMtuServiceImpl mtuService = new NicProfileMtuServiceImpl(); + mtuService.routerJoinDao = mock(com.cloud.api.query.dao.DomainRouterJoinDao.class); + mtuService.networksDao = testOrchestrator._networksDao; + mtuService.entityManager = testOrchestrator._entityMgr; + testOrchestrator.nicProfileMtuService = mtuService; + testOrchestrator.nicImportService = mock(NicImportService.class); + testOrchestrator.nicMigrationService = mock(NicMigrationService.class); + testOrchestrator.networkHostSetupService = mock(NetworkHostSetupService.class); + testOrchestrator.networkUpdateSequenceService = mock(NetworkUpdateSequenceService.class); + testOrchestrator.networkServiceChangeCleanupService = mock(NetworkServiceChangeCleanupService.class); + testOrchestrator.networkRuleReprogrammingService = mock(NetworkRuleReprogrammingService.class); + testOrchestrator.networkResourceCleanupService = mock(NetworkResourceCleanupService.class); + testOrchestrator.persistentNetworkSetupService = mock(PersistentNetworkSetupService.class); + testOrchestrator.networkVlanRangeCleanupService = mock(NetworkVlanRangeCleanupService.class); + RouterDefaultDnsUpdateServiceImpl dnsUpdateService = new RouterDefaultDnsUpdateServiceImpl(); + dnsUpdateService.routerDao = testOrchestrator.routerDao; + dnsUpdateService.routerNetworkDao = testOrchestrator.routerNetworkDao; + dnsUpdateService.vpcManager = testOrchestrator._vpcMgr; + dnsUpdateService.networksDao = testOrchestrator._networksDao; + testOrchestrator.routerDefaultDnsUpdateService = dnsUpdateService; + DhcpServiceProvider provider = mock(DhcpServiceProvider.class); Map capabilities = new HashMap(); @@ -150,11 +244,236 @@ public void setUp() { List networkGurus = new ArrayList(); networkGurus.add(guru); testOrchestrator.networkGurus = networkGurus; + NicProfileLifecycleMappingServiceImpl lifecycleMappingService = new NicProfileLifecycleMappingServiceImpl(); + lifecycleMappingService.nicDao = testOrchestrator._nicDao; + lifecycleMappingService.networksDao = testOrchestrator._networksDao; + lifecycleMappingService.networkModel = testOrchestrator._networkModel; + lifecycleMappingService.setNetworkGurus(networkGurus); + testOrchestrator.nicProfileLifecycleMappingService = lifecycleMappingService; when(networkOffering.getGuestType()).thenReturn(GuestType.L2); when(networkOffering.getId()).thenReturn(networkOfferingId); } + @Test + public void testProcessConnectDelegatesToNetworkHostSetupService() throws ConnectionException { + Host host = mock(Host.class); + StartupCommand startup = mock(StartupCommand.class); + + testOrchestrator.processConnect(host, startup, true); + + verify(testOrchestrator.networkHostSetupService).processConnect(host, startup, true); + } + + @Test + public void canUpdateInSequenceDelegatesToService() { + Network network = mock(Network.class); + when(testOrchestrator.networkUpdateSequenceService.canUpdateInSequence(network, true)).thenReturn(true); + + Assert.assertTrue(testOrchestrator.canUpdateInSequence(network, true)); + + verify(testOrchestrator.networkUpdateSequenceService).canUpdateInSequence(network, true); + } + + @Test + public void deleteVlansInNetworkDelegatesToNetworkVlanRangeCleanupService() { + NetworkVO network = mock(NetworkVO.class); + Account caller = mock(Account.class); + Pair> expected = new Pair<>(true, Collections.emptyList()); + when(testOrchestrator.networkVlanRangeCleanupService.deleteVlansInNetwork(network, 42L, caller)).thenReturn(expected); + + Pair> result = testOrchestrator.deleteVlansInNetwork(network, 42L, caller); + + Assert.assertSame(expected, result); + verify(testOrchestrator.networkVlanRangeCleanupService).deleteVlansInNetwork(network, 42L, caller); + } + + @Test + public void applyProfileToNicDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + NicVO nic = mock(NicVO.class); + NicProfile profile = mock(NicProfile.class); + when(service.applyProfileToNic(nic, profile, 2)).thenReturn(3); + + Integer result = testOrchestrator.applyProfileToNic(nic, profile, 2); + + Assert.assertEquals(Integer.valueOf(3), result); + verify(service).applyProfileToNic(nic, profile, 2); + } + + @Test + public void applyProfileToNicForReleaseDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + NicVO nic = mock(NicVO.class); + NicProfile profile = mock(NicProfile.class); + + testOrchestrator.applyProfileToNicForRelease(nic, profile); + + verify(service).applyProfileToNicForRelease(nic, profile); + } + + @Test + public void applyProfileToNetworkDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + NetworkVO network = mock(NetworkVO.class); + NetworkProfile profile = mock(NetworkProfile.class); + + testOrchestrator.applyProfileToNetwork(network, profile); + + verify(service).applyProfileToNetwork(network, profile); + } + + @Test + public void toNicTODelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + NicVO nic = mock(NicVO.class); + NicProfile profile = mock(NicProfile.class); + NetworkVO network = mock(NetworkVO.class); + NicTO expected = new NicTO(); + when(service.toNicTO(nic, profile, network)).thenReturn(expected); + + NicTO result = testOrchestrator.toNicTO(nic, profile, network); + + Assert.assertSame(expected, result); + verify(service).toNicTO(nic, profile, network); + } + + @Test + public void getNicProfileForVmDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + Network network = mock(Network.class); + NicProfile requested = mock(NicProfile.class); + VirtualMachine vm = mock(VirtualMachine.class); + NicProfile expected = new NicProfile(); + when(service.getNicProfileForVm(network, requested, vm)).thenReturn(expected); + + NicProfile result = testOrchestrator.getNicProfileForVm(network, requested, vm); + + Assert.assertSame(expected, result); + verify(service).getNicProfileForVm(network, requested, vm); + } + + @Test + public void getNicProfilesByVmIdDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + List expected = Collections.singletonList(new NicProfile()); + when(service.getNicProfiles(42L, Hypervisor.HypervisorType.KVM)).thenReturn(expected); + + List result = testOrchestrator.getNicProfiles(42L, Hypervisor.HypervisorType.KVM); + + Assert.assertSame(expected, result); + verify(service).getNicProfiles(42L, Hypervisor.HypervisorType.KVM); + } + + @Test + public void getNicProfilesByVmDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + VirtualMachine vm = mock(VirtualMachine.class); + List expected = Collections.singletonList(new NicProfile()); + when(service.getNicProfiles(vm)).thenReturn(expected); + + List result = testOrchestrator.getNicProfiles(vm); + + Assert.assertSame(expected, result); + verify(service).getNicProfiles(vm); + } + + @Test + public void getSystemVMAccessDetailsDelegatesToLifecycleMappingService() { + NicProfileLifecycleMappingService service = mock(NicProfileLifecycleMappingService.class); + testOrchestrator.nicProfileLifecycleMappingService = service; + VirtualMachine vm = mock(VirtualMachine.class); + Map expected = Collections.singletonMap("key", "value"); + when(service.getSystemVMAccessDetails(vm)).thenReturn(expected); + + Map result = testOrchestrator.getSystemVMAccessDetails(vm); + + Assert.assertSame(expected, result); + verify(service).getSystemVMAccessDetails(vm); + } + + @Test + public void finalizeServicesAndProvidersForNetworkDelegatesToProviderMappingService() { + NetworkProviderMappingService mappingService = mock(NetworkProviderMappingService.class); + testOrchestrator.networkProviderMappingService = mappingService; + Long physicalNetworkId = 42L; + Map expected = Collections.singletonMap(Service.Dhcp.getName(), Network.Provider.VirtualRouter.getName()); + when(mappingService.finalizeServicesAndProvidersForNetwork(networkOffering, physicalNetworkId)).thenReturn(expected); + + Map result = testOrchestrator.finalizeServicesAndProvidersForNetwork(networkOffering, physicalNetworkId); + + Assert.assertSame(expected, result); + verify(mappingService).finalizeServicesAndProvidersForNetwork(networkOffering, physicalNetworkId); + } + + @Test + public void configureUpdateInSequenceDelegatesToService() { + Network network = mock(Network.class); + + testOrchestrator.configureUpdateInSequence(network); + + verify(testOrchestrator.networkUpdateSequenceService).configureUpdateInSequence(network); + } + + @Test + public void getResourceCountDelegatesToService() { + Network network = mock(Network.class); + when(testOrchestrator.networkUpdateSequenceService.getResourceCount(network)).thenReturn(2); + + Assert.assertEquals(2, testOrchestrator.getResourceCount(network)); + + verify(testOrchestrator.networkUpdateSequenceService).getResourceCount(network); + } + + @Test + public void finalizeUpdateInSequenceDelegatesToService() { + Network network = mock(Network.class); + + testOrchestrator.finalizeUpdateInSequence(network, false); + + verify(testOrchestrator.networkUpdateSequenceService).finalizeUpdateInSequence(network, false); + } + + @Test + public void getServicesNotSupportedInNewOfferingDelegatesToService() { + Network network = mock(Network.class); + List services = Collections.singletonList(Service.StaticNat.getName()); + when(testOrchestrator.networkServiceChangeCleanupService.getServicesNotSupportedInNewOffering(network, 2L)).thenReturn(services); + + Assert.assertEquals(services, testOrchestrator.getServicesNotSupportedInNewOffering(network, 2L)); + + verify(testOrchestrator.networkServiceChangeCleanupService).getServicesNotSupportedInNewOffering(network, 2L); + } + + @Test + public void cleanupConfigForServicesInNetworkDelegatesToService() { + Network network = mock(Network.class); + List services = Collections.singletonList(Service.Firewall.getName()); + + testOrchestrator.cleanupConfigForServicesInNetwork(services, network); + + verify(testOrchestrator.networkServiceChangeCleanupService).cleanupConfigForServicesInNetwork(services, network); + } + + @Test + public void reprogramNetworkRulesDelegatesToService() throws ResourceUnavailableException { + Network network = mock(Network.class); + Account caller = mock(Account.class); + long networkId = 123L; + when(testOrchestrator.networkRuleReprogrammingService.reprogramNetworkRules(networkId, caller, network)).thenReturn(true); + + Assert.assertTrue(testOrchestrator.reprogramNetworkRules(networkId, caller, network)); + + verify(testOrchestrator.networkRuleReprogrammingService).reprogramNetworkRules(networkId, caller, network); + } + @Test public void testRemoveDhcpServiceWithNic() { // make local mocks @@ -406,7 +725,7 @@ private void configureTestConfigureNicProfileBasedOnRequestedIpTests(NicProfile private void verifyAndAssert(String requestedIpv4Address, String ipv4Gateway, String ipv4Netmask, NicProfile nicProfile, int acquireLockAndCheckIfIpv4IsFreeTimes, int nextMacAddressTimes) { - verify(testOrchestrator, times(acquireLockAndCheckIfIpv4IsFreeTimes)).acquireLockAndCheckIfIpv4IsFree(Mockito.any(Network.class), Mockito.anyString()); + verify(requestedNicIpReservationService, times(acquireLockAndCheckIfIpv4IsFreeTimes)).acquireLockAndCheckIfIpv4IsFree(Mockito.any(Network.class), Mockito.anyString()); try { verify(testOrchestrator._networkModel, times(nextMacAddressTimes)).getNextAvailableMacAddressInNetwork(Mockito.anyLong()); } catch (InsufficientAddressCapacityException e) { @@ -464,7 +783,7 @@ private void executeTestAcquireLockAndCheckIfIpv4IsFree(IPAddressVO.State state, verify(testOrchestrator._ipAddressDao, Mockito.times(acquireLockTimes)).acquireInLockTable(Mockito.anyLong()); verify(testOrchestrator._ipAddressDao, Mockito.times(releaseFromLockTimes)).releaseFromLockTable(Mockito.anyLong()); verify(testOrchestrator._ipAddressDao, Mockito.times(updateTimes)).update(Mockito.anyLong(), Mockito.any(IPAddressVO.class)); - verify(testOrchestrator, Mockito.times(validateTimes)).validateLockedRequestedIp(Mockito.any(IPAddressVO.class), Mockito.any(IPAddressVO.class)); + verify(requestedNicIpReservationService, Mockito.times(validateTimes)).validateLockedRequestedIp(Mockito.any(IPAddressVO.class), Mockito.any(IPAddressVO.class)); } @Test(expected = InvalidParameterValueException.class) @@ -720,136 +1039,6 @@ public void testPrepareNicNetworkRouterNoDnsVm() { Assert.assertEquals(ip6Dns[1], profile.getIPv6Dns2()); } - @Test - public void testGetNetworkGatewayAndNetmaskForNicImportAdvancedZone() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - String ipAddress = "10.1.1.10"; - - String networkGateway = "10.1.1.1"; - String networkNetmask = "255.255.255.0"; - String networkCidr = "10.1.1.0/24"; - Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); - Mockito.when(network.getGateway()).thenReturn(networkGateway); - Mockito.when(network.getCidr()).thenReturn(networkCidr); - Pair pair = testOrchestrator.getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, ipAddress); - Assert.assertNotNull(pair); - Assert.assertEquals(networkGateway, pair.first()); - Assert.assertEquals(networkNetmask, pair.second()); - } - - @Test - public void testGetNetworkGatewayAndNetmaskForNicImportBasicZone() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); - String ipAddress = "172.1.1.10"; - - String defaultNetworkGateway = "172.1.1.1"; - String defaultNetworkNetmask = "255.255.255.0"; - VlanVO vlan = Mockito.mock(VlanVO.class); - Mockito.when(vlan.getVlanGateway()).thenReturn(defaultNetworkGateway); - Mockito.when(vlan.getVlanNetmask()).thenReturn(defaultNetworkNetmask); - Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); - Mockito.when(ipAddressVO.getVlanId()).thenReturn(1L); - Mockito.when(testOrchestrator._vlanDao.findById(1L)).thenReturn(vlan); - Mockito.when(testOrchestrator._ipAddressDao.findByIp(ipAddress)).thenReturn(ipAddressVO); - Pair pair = testOrchestrator.getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, ipAddress); - Assert.assertNotNull(pair); - Assert.assertEquals(defaultNetworkGateway, pair.first()); - Assert.assertEquals(defaultNetworkNetmask, pair.second()); - } - - @Test - public void testGetGuestIpForNicImportL2Network() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.L2); - Assert.assertNull(testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses)); - } - - @Test - public void testGetGuestIpForNicImportAdvancedZone() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated); - Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); - String ipAddress = "10.1.10.10"; - Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); - Mockito.when(testOrchestrator._ipAddrMgr.acquireGuestIpAddress(network, ipAddress)).thenReturn(ipAddress); - String guestIp = testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses); - Assert.assertEquals(ipAddress, guestIp); - } - - @Test - public void testGetGuestIpForNicImportBasicZoneAutomaticIP() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared); - Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); - long networkId = 1L; - long dataCenterId = 1L; - String freeIp = "172.10.10.10"; - IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); - Ip ip = mock(Ip.class); - Mockito.when(ip.addr()).thenReturn(freeIp); - Mockito.when(ipAddressVO.getAddress()).thenReturn(ip); - Mockito.when(ipAddressVO.getState()).thenReturn(State.Free); - Mockito.when(network.getId()).thenReturn(networkId); - Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); - Mockito.when(testOrchestrator._ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(networkId, dataCenterId, State.Free)).thenReturn(ipAddressVO); - String ipAddress = testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses); - Assert.assertEquals(freeIp, ipAddress); - } - - @Test - public void testGetGuestIpForNicImportBasicZoneManualIP() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared); - Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); - long networkId = 1L; - long dataCenterId = 1L; - String requestedIp = "172.10.10.10"; - IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); - Ip ip = mock(Ip.class); - Mockito.when(ip.addr()).thenReturn(requestedIp); - Mockito.when(ipAddressVO.getAddress()).thenReturn(ip); - Mockito.when(ipAddressVO.getState()).thenReturn(State.Free); - Mockito.when(network.getId()).thenReturn(networkId); - Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); - Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp); - Mockito.when(testOrchestrator._ipAddressDao.findByIpAndSourceNetworkId(networkId, requestedIp)).thenReturn(ipAddressVO); - String ipAddress = testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses); - Assert.assertEquals(requestedIp, ipAddress); - } - - @Test(expected = CloudRuntimeException.class) - public void testGetGuestIpForNicImportBasicUsedIP() { - Network network = Mockito.mock(Network.class); - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared); - Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); - long networkId = 1L; - long dataCenterId = 1L; - String requestedIp = "172.10.10.10"; - IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); - Ip ip = mock(Ip.class); - Mockito.when(ip.addr()).thenReturn(requestedIp); - Mockito.when(ipAddressVO.getAddress()).thenReturn(ip); - Mockito.when(ipAddressVO.getState()).thenReturn(State.Allocated); - Mockito.when(network.getId()).thenReturn(networkId); - Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); - Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp); - Mockito.when(testOrchestrator._ipAddressDao.findByIp(requestedIp)).thenReturn(ipAddressVO); - testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses); - } - @Test public void testShutdownNetworkAcquireLockFailed() { ReservationContext reservationContext = Mockito.mock(ReservationContext.class); @@ -897,117 +1086,44 @@ public void testShutdownNetworkInImplementingState() { verify(testOrchestrator._networksDao, times(1)).releaseFromLockTable(networkId); } - @Test(expected = InsufficientVirtualNetworkCapacityException.class) - public void testImportNicAcquireGuestIPFailed() throws Exception { - DataCenter dataCenter = Mockito.mock(DataCenter.class); - VirtualMachine vm = mock(VirtualMachine.class); - Network network = Mockito.mock(Network.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated); - Mockito.when(network.getNetworkOfferingId()).thenReturn(networkOfferingId); - long dataCenterId = 1L; - Mockito.when(network.getDataCenterId()).thenReturn(dataCenterId); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - String ipAddress = "10.1.10.10"; - Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); - Mockito.when(testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses)).thenReturn(null); - Mockito.when(testOrchestrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); - String macAddress = "02:01:01:82:00:01"; - int deviceId = 0; - testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); - } - - @Test(expected = InsufficientVirtualNetworkCapacityException.class) - public void testImportNicAutoAcquireGuestIPFailed() throws Exception { - DataCenter dataCenter = Mockito.mock(DataCenter.class); - VirtualMachine vm = mock(VirtualMachine.class); - Network network = Mockito.mock(Network.class); - Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated); - Mockito.when(network.getNetworkOfferingId()).thenReturn(networkOfferingId); - long dataCenterId = 1L; - Mockito.when(network.getDataCenterId()).thenReturn(dataCenterId); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - String ipAddress = "auto"; - Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); - Mockito.when(testOrchestrator.getSelectedIpForNicImport(network, dataCenter, ipAddresses)).thenReturn(null); - Mockito.when(testOrchestrator._networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); - String macAddress = "02:01:01:82:00:01"; - int deviceId = 0; - testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); - } - - @Test - public void testImportNicNoIP4Address() throws Exception { - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Long vmId = 1L; - Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; - VirtualMachine vm = mock(VirtualMachine.class); - Mockito.when(vm.getId()).thenReturn(vmId); - Mockito.when(vm.getHypervisorType()).thenReturn(hypervisorType); - Long networkId = 1L; - Network network = Mockito.mock(Network.class); - Mockito.when(network.getId()).thenReturn(networkId); - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(ipAddresses.getIp4Address()).thenReturn(null); - URI broadcastUri = URI.create("vlan://123"); - NicVO nic = mock(NicVO.class); - Mockito.when(nic.getBroadcastUri()).thenReturn(broadcastUri); - String macAddress = "02:01:01:82:00:01"; - int deviceId = 1; - Integer networkRate = 200; - Mockito.when(testOrchestrator._networkModel.getNetworkRate(networkId, vmId)).thenReturn(networkRate); - Mockito.when(testOrchestrator._networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(false); - Mockito.when(testOrchestrator._networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag"); - try (MockedStatic transactionMocked = Mockito.mockStatic(Transaction.class)) { - transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic); - Pair nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); - verify(testOrchestrator._networkModel, times(1)).getNetworkRate(networkId, vmId); - verify(testOrchestrator._networkModel, times(1)).isSecurityGroupSupportedInNetwork(network); - verify(testOrchestrator._networkModel, times(1)).getNetworkTag(Hypervisor.HypervisorType.KVM, network); - assertEquals(deviceId, nicProfileIntegerPair.second().intValue()); - NicProfile nicProfile = nicProfileIntegerPair.first(); - assertEquals(broadcastUri, nicProfile.getBroadCastUri()); - assertEquals(networkRate, nicProfile.getNetworkRate()); - assertFalse(nicProfile.isSecurityGroupEnabled()); - assertEquals("testtag", nicProfile.getName()); - } + @Test + public void testPrepareNicForMigrationDelegatesToNicMigrationService() { + VirtualMachineProfile vm = mock(VirtualMachineProfile.class); + DeployDestination dest = mock(DeployDestination.class); + + testOrchestrator.prepareNicForMigration(vm, dest); + + verify(testOrchestrator.nicMigrationService, times(1)).prepareNicForMigration(vm, dest); } @Test - public void testImportNicWithIP4Address() throws Exception { - DataCenter dataCenter = Mockito.mock(DataCenter.class); - Long vmId = 1L; - Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; - VirtualMachine vm = mock(VirtualMachine.class); - Mockito.when(vm.getId()).thenReturn(vmId); - Mockito.when(vm.getHypervisorType()).thenReturn(hypervisorType); - Long networkId = 1L; - Network network = Mockito.mock(Network.class); - Mockito.when(network.getId()).thenReturn(networkId); - String ipAddress = "10.1.10.10"; - Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); - Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); - URI broadcastUri = URI.create("vlan://123"); - NicVO nic = mock(NicVO.class); - Mockito.when(nic.getBroadcastUri()).thenReturn(broadcastUri); - String macAddress = "02:01:01:82:00:01"; - int deviceId = 1; - Integer networkRate = 200; - Mockito.when(testOrchestrator._networkModel.getNetworkRate(networkId, vmId)).thenReturn(networkRate); - Mockito.when(testOrchestrator._networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(false); - Mockito.when(testOrchestrator._networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag"); - try (MockedStatic transactionMocked = Mockito.mockStatic(Transaction.class)) { - transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic); - Pair nicProfileIntegerPair = testOrchestrator.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); - verify(testOrchestrator, times(1)).getSelectedIpForNicImport(network, dataCenter, ipAddresses); - verify(testOrchestrator._networkModel, times(1)).getNetworkRate(networkId, vmId); - verify(testOrchestrator._networkModel, times(1)).isSecurityGroupSupportedInNetwork(network); - verify(testOrchestrator._networkModel, times(1)).getNetworkTag(Hypervisor.HypervisorType.KVM, network); - assertEquals(deviceId, nicProfileIntegerPair.second().intValue()); - NicProfile nicProfile = nicProfileIntegerPair.first(); - assertEquals(broadcastUri, nicProfile.getBroadCastUri()); - assertEquals(networkRate, nicProfile.getNetworkRate()); - assertFalse(nicProfile.isSecurityGroupEnabled()); - assertEquals("testtag", nicProfile.getName()); - } + public void testPrepareAllNicsForMigrationDelegatesToNicMigrationService() { + VirtualMachineProfile vm = mock(VirtualMachineProfile.class); + DeployDestination dest = mock(DeployDestination.class); + + testOrchestrator.prepareAllNicsForMigration(vm, dest); + + verify(testOrchestrator.nicMigrationService, times(1)).prepareAllNicsForMigration(vm, dest); } + + @Test + public void testCommitNicForMigrationDelegatesToNicMigrationService() { + VirtualMachineProfile src = mock(VirtualMachineProfile.class); + VirtualMachineProfile dst = mock(VirtualMachineProfile.class); + + testOrchestrator.commitNicForMigration(src, dst); + + verify(testOrchestrator.nicMigrationService, times(1)).commitNicForMigration(src, dst); + } + + @Test + public void testRollbackNicForMigrationDelegatesToNicMigrationService() { + VirtualMachineProfile src = mock(VirtualMachineProfile.class); + VirtualMachineProfile dst = mock(VirtualMachineProfile.class); + + testOrchestrator.rollbackNicForMigration(src, dst); + + verify(testOrchestrator.nicMigrationService, times(1)).rollbackNicForMigration(src, dst); + } + } diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingServiceImplTest.java new file mode 100644 index 000000000000..85db8269d627 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkProviderMappingServiceImplTest.java @@ -0,0 +1,204 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.exception.UnsupportedServiceException; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.NetworkOfferingServiceMapVO; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; + +@RunWith(MockitoJUnitRunner.class) +public class NetworkProviderMappingServiceImplTest { + + private static final long OFFERING_ID = 101L; + private static final long NETWORK_ID = 202L; + private static final long PHYSICAL_NETWORK_ID = 303L; + + @Mock + private NetworkOfferingServiceMapDao networkOfferingServiceMapDao; + + @Mock + private NetworkModel networkModel; + + @Mock + private PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao; + + @Mock + private NetworkServiceMapDao networkServiceMapDao; + + @Mock + private NetworkOffering offering; + + @InjectMocks + private NetworkProviderMappingServiceImpl service; + + @Test + public void finalizeServicesAndProvidersForNetworkReturnsExplicitProviderMap() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.singletonList(mapping(Service.Dhcp, Provider.VirtualRouter))); + + Map result = service.finalizeServicesAndProvidersForNetwork(offering, null); + + assertEquals(1, result.size()); + assertEquals(Provider.VirtualRouter.getName(), result.get(Service.Dhcp.getName())); + } + + @Test + public void finalizeServicesAndProvidersForNetworkUsesDefaultUniqueProviderWhenProviderIsNull() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.singletonList(mapping(Service.Dns, null))); + when(networkModel.getDefaultUniqueProviderForService(Service.Dns.getName())).thenReturn(Provider.VirtualRouter); + + Map result = service.finalizeServicesAndProvidersForNetwork(offering, null); + + assertEquals(Provider.VirtualRouter.getName(), result.get(Service.Dns.getName())); + verify(networkModel).getDefaultUniqueProviderForService(Service.Dns.getName()); + } + + @Test + public void finalizeServicesAndProvidersForNetworkKeepsFirstProviderForDuplicateService() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Arrays.asList( + mapping(Service.Lb, Provider.VirtualRouter), + mapping(Service.Lb, Provider.Netscaler))); + + Map result = service.finalizeServicesAndProvidersForNetwork(offering, null); + + assertEquals(1, result.size()); + assertEquals(Provider.VirtualRouter.getName(), result.get(Service.Lb.getName())); + } + + @Test + public void finalizeServicesAndProvidersForNetworkSkipsDefaultLookupForDuplicateServiceAfterFirstProvider() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Arrays.asList( + mapping(Service.UserData, Provider.VirtualRouter), + mapping(Service.UserData, null))); + + service.finalizeServicesAndProvidersForNetwork(offering, null); + + verify(networkModel, never()).getDefaultUniqueProviderForService(anyString()); + } + + @Test + public void finalizeServicesAndProvidersForNetworkChecksPhysicalNetworkProviderSupportWhenPhysicalNetworkIsPresent() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.singletonList(mapping(Service.StaticNat, Provider.JuniperSRX))); + when(physicalNetworkServiceProviderDao.isServiceProviderEnabled(PHYSICAL_NETWORK_ID, Provider.JuniperSRX.getName(), Service.StaticNat.getName())).thenReturn(true); + + Map result = service.finalizeServicesAndProvidersForNetwork(offering, PHYSICAL_NETWORK_ID); + + assertEquals(Provider.JuniperSRX.getName(), result.get(Service.StaticNat.getName())); + verify(physicalNetworkServiceProviderDao).isServiceProviderEnabled(PHYSICAL_NETWORK_ID, Provider.JuniperSRX.getName(), Service.StaticNat.getName()); + } + + @Test + public void finalizeServicesAndProvidersForNetworkChecksPhysicalNetworkProviderSupportAfterDefaultProviderResolution() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.singletonList(mapping(Service.Dhcp, null))); + when(networkModel.getDefaultUniqueProviderForService(Service.Dhcp.getName())).thenReturn(Provider.VirtualRouter); + when(physicalNetworkServiceProviderDao.isServiceProviderEnabled(PHYSICAL_NETWORK_ID, Provider.VirtualRouter.getName(), Service.Dhcp.getName())).thenReturn(true); + + Map result = service.finalizeServicesAndProvidersForNetwork(offering, PHYSICAL_NETWORK_ID); + + assertEquals(Provider.VirtualRouter.getName(), result.get(Service.Dhcp.getName())); + verify(physicalNetworkServiceProviderDao).isServiceProviderEnabled(PHYSICAL_NETWORK_ID, Provider.VirtualRouter.getName(), Service.Dhcp.getName()); + } + + @Test + public void finalizeServicesAndProvidersForNetworkThrowsWhenProviderUnsupportedOnPhysicalNetwork() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.singletonList(mapping(Service.Firewall, Provider.PaloAlto))); + when(physicalNetworkServiceProviderDao.isServiceProviderEnabled(PHYSICAL_NETWORK_ID, Provider.PaloAlto.getName(), Service.Firewall.getName())).thenReturn(false); + + UnsupportedServiceException exception = assertThrows(UnsupportedServiceException.class, + () -> service.finalizeServicesAndProvidersForNetwork(offering, PHYSICAL_NETWORK_ID)); + + assertTrue(exception.getMessage().contains(Provider.PaloAlto.getName())); + assertTrue(exception.getMessage().contains(Service.Firewall.getName())); + } + + @Test + public void finalizeServicesAndProvidersForNetworkSkipsPhysicalNetworkCheckWhenPhysicalNetworkIsNull() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.singletonList(mapping(Service.Dhcp, Provider.VirtualRouter))); + + service.finalizeServicesAndProvidersForNetwork(offering, null); + + verify(physicalNetworkServiceProviderDao, never()).isServiceProviderEnabled(anyLong(), anyString(), anyString()); + } + + @Test + public void finalizeServicesAndProvidersForNetworkReturnsEmptyMapWhenOfferingHasNoServices() { + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingServiceMapDao.listByNetworkOfferingId(OFFERING_ID)).thenReturn(Collections.emptyList()); + + Map result = service.finalizeServicesAndProvidersForNetwork(offering, null); + + assertTrue(result.isEmpty()); + } + + @Test + public void getNetworkProvidersConvertsDistinctProviderNamesAndPreservesOrder() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Arrays.asList(Provider.VirtualRouter.getName(), Provider.Netscaler.getName())); + + List result = service.getNetworkProviders(NETWORK_ID); + + assertEquals(2, result.size()); + assertSame(Provider.VirtualRouter, result.get(0)); + assertSame(Provider.Netscaler, result.get(1)); + } + + @Test + public void getNetworkProvidersReturnsEmptyListWhenDaoReturnsNoProviders() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.emptyList()); + + List result = service.getNetworkProviders(NETWORK_ID); + + assertTrue(result.isEmpty()); + } + + private NetworkOfferingServiceMapVO mapping(final Service service, final Provider provider) { + return new NetworkOfferingServiceMapVO(OFFERING_ID, service, provider); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionServiceImplTest.java new file mode 100644 index 000000000000..bd4173189786 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkProviderResolutionServiceImplTest.java @@ -0,0 +1,438 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkServiceMapVO; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.DnsServiceProvider; +import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.StaticNatServiceProvider; +import com.cloud.network.element.UserDataServiceProvider; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.offering.NetworkOffering; +import com.cloud.offerings.dao.NetworkOfferingDetailsDao; +import com.cloud.utils.db.EntityManager; + +/** + * Focused tests for {@link NetworkProviderResolutionServiceImpl} -- the + * Phase 4 extraction of network-provider/element lookups out of + * {@link NetworkOrchestrator}. + * + * Behavior here is also exercised indirectly through the orchestrator's + * delegating wrappers and call sites such as {@code removeNic} that + * resolve DHCP / DNS providers; these tests target the service directly + * so future refactors of the orchestrator cannot silently drop coverage. + */ +@RunWith(MockitoJUnitRunner.class) +public class NetworkProviderResolutionServiceImplTest { + + @Mock + private NetworkServiceMapDao networkServiceMapDao; + + @Mock + private NetworkModel networkModel; + + @Mock + private NetworkOfferingDetailsDao networkOfferingDetailsDao; + + @Mock + private EntityManager entityManager; + + @InjectMocks + private NetworkProviderResolutionServiceImpl service; + + private Network network; + private static final long NETWORK_ID = 100L; + private static final long OFFERING_ID = 7L; + + @Before + public void setUp() { + network = Mockito.mock(Network.class); + Mockito.when(network.getId()).thenReturn(NETWORK_ID); + } + + private NetworkServiceMapVO mapping(Service service, Provider provider) { + NetworkServiceMapVO m = Mockito.mock(NetworkServiceMapVO.class); + Mockito.when(m.getService()).thenReturn(service.getName()); + Mockito.when(m.getProvider()).thenReturn(provider.getName()); + return m; + } + + // --------------------------------------------------------------------- + // getProvidersForServiceInNetwork + // --------------------------------------------------------------------- + + @Test + public void getProvidersForServiceInNetworkReturnsProviderWhenConfigured() { + List mappings = Arrays.asList(mapping(Service.Dhcp, Provider.VirtualRouter)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + + List providers = service.getProvidersForServiceInNetwork(network, Service.Dhcp); + + assertNotNull(providers); + assertEquals(1, providers.size()); + assertEquals(Provider.VirtualRouter, providers.get(0)); + } + + @Test + public void getProvidersForServiceInNetworkReturnsNullWhenServiceNotMapped() { + List mappings = Arrays.asList(mapping(Service.Dhcp, Provider.VirtualRouter)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + + // Asking for Lb -- not mapped -- yields null per the original contract. + assertNull(service.getProvidersForServiceInNetwork(network, Service.Lb)); + } + + @Test + public void getProvidersForServiceInNetworkReturnsNullWhenNoMappings() { + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)) + .thenReturn(Collections.emptyList()); + + assertNull(service.getProvidersForServiceInNetwork(network, Service.Dhcp)); + } + + // --------------------------------------------------------------------- + // getElementForServiceInNetwork + // --------------------------------------------------------------------- + + @Test + public void getElementForServiceInNetworkReturnsNullWhenNoProvider() { + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)) + .thenReturn(Collections.emptyList()); + + assertNull(service.getElementForServiceInNetwork(network, Service.StaticNat)); + } + + @Test + public void getElementForServiceInNetworkReturnsNullForNonLbWithMultipleProviders() { + // Two providers configured for StaticNat -- not allowed; expect null. + List mappings = Arrays.asList( + mapping(Service.StaticNat, Provider.VirtualRouter), + mapping(Service.StaticNat, Provider.JuniperSRX)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + + assertNull(service.getElementForServiceInNetwork(network, Service.StaticNat)); + } + + @Test + public void getElementForServiceInNetworkReturnsSingleElement() { + List mappings = Arrays.asList(mapping(Service.StaticNat, Provider.VirtualRouter)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + NetworkElement element = Mockito.mock(NetworkElement.class); + Mockito.when(element.getName()).thenReturn("VirtualRouter"); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())) + .thenReturn(element); + + List result = service.getElementForServiceInNetwork(network, Service.StaticNat); + + assertNotNull(result); + assertEquals(1, result.size()); + assertSame(element, result.get(0)); + } + + @Test + public void getElementForServiceInNetworkAllowsMultipleLbProviders() { + List mappings = Arrays.asList( + mapping(Service.Lb, Provider.VirtualRouter), + mapping(Service.Lb, Provider.InternalLbVm)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + NetworkElement vr = Mockito.mock(NetworkElement.class); + Mockito.when(vr.getName()).thenReturn("VirtualRouter"); + NetworkElement ilb = Mockito.mock(NetworkElement.class); + Mockito.when(ilb.getName()).thenReturn("InternalLbVm"); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())).thenReturn(vr); + Mockito.when(networkModel.getElementImplementingProvider(Provider.InternalLbVm.getName())).thenReturn(ilb); + + List result = service.getElementForServiceInNetwork(network, Service.Lb); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(vr)); + assertTrue(result.contains(ilb)); + } + + // --------------------------------------------------------------------- + // getStaticNatProviderForNetwork + // --------------------------------------------------------------------- + + @Test + public void getStaticNatProviderForNetworkReturnsCastElement() { + List mappings = Arrays.asList(mapping(Service.StaticNat, Provider.VirtualRouter)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + StaticNatServiceProvider snp = Mockito.mock(StaticNatServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(((NetworkElement) snp).getName()).thenReturn("VirtualRouter"); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())) + .thenReturn((NetworkElement) snp); + + StaticNatServiceProvider result = service.getStaticNatProviderForNetwork(network); + + assertSame(snp, result); + } + + // --------------------------------------------------------------------- + // getLoadBalancingProviderForNetwork + // --------------------------------------------------------------------- + + @Test + public void getLoadBalancingProviderForNetworkReturnsSingleElement() { + List mappings = Arrays.asList(mapping(Service.Lb, Provider.VirtualRouter)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + LoadBalancingServiceProvider lbp = Mockito.mock(LoadBalancingServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(((NetworkElement) lbp).getName()).thenReturn("VirtualRouter"); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())) + .thenReturn((NetworkElement) lbp); + + LoadBalancingServiceProvider result = service.getLoadBalancingProviderForNetwork(network, Scheme.Public); + + assertSame(lbp, result); + } + + @Test + public void getLoadBalancingProviderForNetworkResolvesPublicLbProviderFromOfferingDetails() { + Mockito.when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + List mappings = Arrays.asList( + mapping(Service.Lb, Provider.VirtualRouter), + mapping(Service.Lb, Provider.InternalLbVm)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + NetworkElement vrElement = Mockito.mock(NetworkElement.class); + NetworkElement ilbElement = Mockito.mock(NetworkElement.class); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())) + .thenReturn(vrElement); + Mockito.when(networkModel.getElementImplementingProvider(Provider.InternalLbVm.getName())) + .thenReturn(ilbElement); + + NetworkOffering offering = Mockito.mock(NetworkOffering.class); + Mockito.when(offering.getId()).thenReturn(OFFERING_ID); + Mockito.when(entityManager.findById(NetworkOffering.class, OFFERING_ID)).thenReturn(offering); + Mockito.when(networkOfferingDetailsDao.getDetail(OFFERING_ID, NetworkOffering.Detail.PublicLbProvider)) + .thenReturn("VirtualRouter"); + + LoadBalancingServiceProvider selected = Mockito.mock(LoadBalancingServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(networkModel.getElementImplementingProvider("VirtualRouter")) + .thenReturn((NetworkElement) selected); + + LoadBalancingServiceProvider result = service.getLoadBalancingProviderForNetwork(network, Scheme.Public); + + assertSame(selected, result); + Mockito.verify(networkOfferingDetailsDao).getDetail(OFFERING_ID, NetworkOffering.Detail.PublicLbProvider); + Mockito.verify(networkOfferingDetailsDao, Mockito.never()) + .getDetail(ArgumentMatchers.anyLong(), ArgumentMatchers.eq(NetworkOffering.Detail.InternalLbProvider)); + } + + @Test + public void getLoadBalancingProviderForNetworkResolvesInternalLbProviderFromOfferingDetails() { + Mockito.when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + List mappings = Arrays.asList( + mapping(Service.Lb, Provider.VirtualRouter), + mapping(Service.Lb, Provider.InternalLbVm)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + NetworkElement vrElement = Mockito.mock(NetworkElement.class); + NetworkElement ilbElement = Mockito.mock(NetworkElement.class); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())) + .thenReturn(vrElement); + Mockito.when(networkModel.getElementImplementingProvider(Provider.InternalLbVm.getName())) + .thenReturn(ilbElement); + + NetworkOffering offering = Mockito.mock(NetworkOffering.class); + Mockito.when(offering.getId()).thenReturn(OFFERING_ID); + Mockito.when(entityManager.findById(NetworkOffering.class, OFFERING_ID)).thenReturn(offering); + Mockito.when(networkOfferingDetailsDao.getDetail(OFFERING_ID, NetworkOffering.Detail.InternalLbProvider)) + .thenReturn("InternalLbVm"); + + LoadBalancingServiceProvider selected = Mockito.mock(LoadBalancingServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(networkModel.getElementImplementingProvider("InternalLbVm")) + .thenReturn((NetworkElement) selected); + + LoadBalancingServiceProvider result = service.getLoadBalancingProviderForNetwork(network, Scheme.Internal); + + assertSame(selected, result); + Mockito.verify(networkOfferingDetailsDao).getDetail(OFFERING_ID, NetworkOffering.Detail.InternalLbProvider); + } + + @Test + public void getLoadBalancingProviderForNetworkThrowsWhenProviderNotConfiguredInOfferingDetails() { + Mockito.when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + List mappings = Arrays.asList( + mapping(Service.Lb, Provider.VirtualRouter), + mapping(Service.Lb, Provider.InternalLbVm)); + Mockito.when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(mappings); + NetworkElement vrElement = Mockito.mock(NetworkElement.class); + NetworkElement ilbElement = Mockito.mock(NetworkElement.class); + Mockito.when(networkModel.getElementImplementingProvider(Provider.VirtualRouter.getName())) + .thenReturn(vrElement); + Mockito.when(networkModel.getElementImplementingProvider(Provider.InternalLbVm.getName())) + .thenReturn(ilbElement); + + NetworkOffering offering = Mockito.mock(NetworkOffering.class); + Mockito.when(offering.getId()).thenReturn(OFFERING_ID); + Mockito.when(entityManager.findById(NetworkOffering.class, OFFERING_ID)).thenReturn(offering); + Mockito.when(networkOfferingDetailsDao.getDetail(OFFERING_ID, NetworkOffering.Detail.PublicLbProvider)) + .thenReturn(null); + + assertThrows(InvalidParameterValueException.class, + () -> service.getLoadBalancingProviderForNetwork(network, Scheme.Public)); + } + + // --------------------------------------------------------------------- + // getPasswordResetProvider / getSSHKeyResetProvider + // --------------------------------------------------------------------- + + @Test + public void getPasswordResetProviderReturnsCastUserDataProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.UserData)) + .thenReturn("VirtualRouter"); + UserDataServiceProvider udp = Mockito.mock(UserDataServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(networkModel.getElementImplementingProvider("VirtualRouter")) + .thenReturn((NetworkElement) udp); + + UserDataServiceProvider result = service.getPasswordResetProvider(network); + + assertSame(udp, result); + } + + @Test + public void getPasswordResetProviderReturnsNullWhenNoProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.UserData)) + .thenReturn(null); + + assertNull(service.getPasswordResetProvider(network)); + Mockito.verify(networkModel, Mockito.never()) + .getElementImplementingProvider(ArgumentMatchers.anyString()); + } + + @Test + public void getSSHKeyResetProviderReturnsCastUserDataProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.UserData)) + .thenReturn("VirtualRouter"); + UserDataServiceProvider udp = Mockito.mock(UserDataServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(networkModel.getElementImplementingProvider("VirtualRouter")) + .thenReturn((NetworkElement) udp); + + UserDataServiceProvider result = service.getSSHKeyResetProvider(network); + + assertSame(udp, result); + } + + @Test + public void getSSHKeyResetProviderReturnsNullWhenNoProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.UserData)) + .thenReturn(null); + + assertNull(service.getSSHKeyResetProvider(network)); + Mockito.verify(networkModel, Mockito.never()) + .getElementImplementingProvider(ArgumentMatchers.anyString()); + } + + // --------------------------------------------------------------------- + // getDhcpServiceProvider + // --------------------------------------------------------------------- + + @Test + public void getDhcpServiceProviderReturnsCastDhcpProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.Dhcp)) + .thenReturn("VirtualRouter"); + DhcpServiceProvider dhcp = Mockito.mock(DhcpServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(networkModel.getElementImplementingProvider("VirtualRouter")) + .thenReturn((NetworkElement) dhcp); + + DhcpServiceProvider result = service.getDhcpServiceProvider(network); + + assertSame(dhcp, result); + } + + @Test + public void getDhcpServiceProviderReturnsNullWhenNoProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.Dhcp)) + .thenReturn(null); + + assertNull(service.getDhcpServiceProvider(network)); + } + + @Test + public void getDhcpServiceProviderReturnsNullWhenElementIsNotDhcpProvider() { + // Configured provider exists but its element is not a DhcpServiceProvider -- + // the contract is to return null rather than ClassCastException. + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.Dhcp)) + .thenReturn("VirtualRouter"); + NetworkElement nonDhcp = Mockito.mock(NetworkElement.class); + Mockito.when(networkModel.getElementImplementingProvider("VirtualRouter")).thenReturn(nonDhcp); + + assertNull(service.getDhcpServiceProvider(network)); + } + + // --------------------------------------------------------------------- + // getDnsServiceProvider + // --------------------------------------------------------------------- + + @Test + public void getDnsServiceProviderReturnsCastDnsProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.Dns)) + .thenReturn("VirtualRouter"); + DnsServiceProvider dns = Mockito.mock(DnsServiceProvider.class, + Mockito.withSettings().extraInterfaces(NetworkElement.class)); + Mockito.when(networkModel.getElementImplementingProvider("VirtualRouter")) + .thenReturn((NetworkElement) dns); + + DnsServiceProvider result = service.getDnsServiceProvider(network); + + assertSame(dns, result); + } + + @Test + public void getDnsServiceProviderReturnsNullWhenNoProvider() { + Mockito.when(networkServiceMapDao.getProviderForServiceInNetwork(NETWORK_ID, Service.Dns)) + .thenReturn(null); + + assertNull(service.getDnsServiceProvider(network)); + Mockito.verify(networkModel, Mockito.never()) + .getElementImplementingProvider(ArgumentMatchers.anyString()); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupServiceImplTest.java new file mode 100644 index 000000000000..5ef35246f24f --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkResourceCleanupServiceImplTest.java @@ -0,0 +1,343 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.annotation.AnnotationService; +import org.apache.cloudstack.annotation.dao.AnnotationDao; +import org.apache.cloudstack.network.RoutedIpv4Manager; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddress; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRule.Purpose; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.network.rules.PortForwardingRuleVO; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.rules.StaticNatRule; +import com.cloud.network.rules.dao.PortForwardingRulesDao; +import com.cloud.network.vpc.NetworkACLManager; +import com.cloud.network.vpc.VpcManager; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.exception.CloudRuntimeException; + +public class NetworkResourceCleanupServiceImplTest { + private static final long NETWORK_ID = 101L; + private static final long CALLER_USER_ID = 202L; + private static final long OFFERING_ID = 303L; + private static final long ZONE_ID = 404L; + private static final long VLAN_ID = 505L; + private static final String NETWORK_UUID = "network-uuid"; + + private NetworkResourceCleanupServiceImpl service; + private NetworkDao networkDao; + private NetworkOfferingDao networkOfferingDao; + private RoutedIpv4Manager routedIpv4Manager; + private RulesManager rulesManager; + private LoadBalancingRulesManager loadBalancingRulesManager; + private FirewallManager firewallManager; + private NetworkACLManager networkACLManager; + private IPAddressDao ipAddressDao; + private IpAddressManager ipAddressManager; + private VpcManager vpcManager; + private AnnotationDao annotationDao; + private PortForwardingRulesDao portForwardingRulesDao; + private FirewallRulesDao firewallRulesDao; + private DataCenterDao dataCenterDao; + private NetworkModel networkModel; + private VlanDao vlanDao; + private NetworkVO network; + private Account caller; + + @Before + public void setUp() throws ResourceUnavailableException { + service = new NetworkResourceCleanupServiceImpl(); + networkDao = mock(NetworkDao.class); + networkOfferingDao = mock(NetworkOfferingDao.class); + routedIpv4Manager = mock(RoutedIpv4Manager.class); + rulesManager = mock(RulesManager.class); + loadBalancingRulesManager = mock(LoadBalancingRulesManager.class); + firewallManager = mock(FirewallManager.class); + networkACLManager = mock(NetworkACLManager.class); + ipAddressDao = mock(IPAddressDao.class); + ipAddressManager = mock(IpAddressManager.class); + vpcManager = mock(VpcManager.class); + annotationDao = mock(AnnotationDao.class); + portForwardingRulesDao = mock(PortForwardingRulesDao.class); + firewallRulesDao = mock(FirewallRulesDao.class); + dataCenterDao = mock(DataCenterDao.class); + networkModel = mock(NetworkModel.class); + vlanDao = mock(VlanDao.class); + network = mock(NetworkVO.class); + caller = mock(Account.class); + + service.networkDao = networkDao; + service.networkOfferingDao = networkOfferingDao; + service.routedIpv4Manager = routedIpv4Manager; + service.rulesManager = rulesManager; + service.loadBalancingRulesManager = loadBalancingRulesManager; + service.firewallManager = firewallManager; + service.networkACLManager = networkACLManager; + service.ipAddressDao = ipAddressDao; + service.ipAddressManager = ipAddressManager; + service.vpcManager = vpcManager; + service.annotationDao = annotationDao; + service.portForwardingRulesDao = portForwardingRulesDao; + service.firewallRulesDao = firewallRulesDao; + service.dataCenterDao = dataCenterDao; + service.networkModel = networkModel; + service.vlanDao = vlanDao; + + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + when(network.getUuid()).thenReturn(NETWORK_UUID); + when(network.getDataCenterId()).thenReturn(ZONE_ID); + when(network.getGuestType()).thenReturn(Network.GuestType.Isolated); + when(networkDao.findById(NETWORK_ID)).thenReturn(network); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(mock(NetworkOfferingVO.class)); + when(routedIpv4Manager.removeBgpPeersFromNetwork(network)).thenReturn(network); + when(rulesManager.revokeAllPFStaticNatRulesForNetwork(NETWORK_ID, CALLER_USER_ID, caller)).thenReturn(true); + when(rulesManager.applyStaticNatForNetwork(network, false, caller, true)).thenReturn(true); + when(loadBalancingRulesManager.removeAllLoadBalanacersForNetwork(NETWORK_ID, caller, CALLER_USER_ID)).thenReturn(true); + when(loadBalancingRulesManager.revokeLoadBalancersForNetwork(network, Scheme.Public)).thenReturn(true); + when(loadBalancingRulesManager.revokeLoadBalancersForNetwork(network, Scheme.Internal)).thenReturn(true); + when(firewallManager.revokeAllFirewallRulesForNetwork(network, CALLER_USER_ID, caller)).thenReturn(true); + when(firewallManager.applyRules(anyList(), eq(true), eq(false))).thenReturn(true); + when(networkACLManager.revokeACLItemsForNetwork(NETWORK_ID)).thenReturn(true); + when(ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Collections.emptyList()); + when(ipAddressManager.applyIpAssociations(network, true)).thenReturn(true); + when(ipAddressManager.applyIpAssociations(eq(network), eq(true), eq(true), anyList())).thenReturn(true); + when(portForwardingRulesDao.listByNetwork(NETWORK_ID)).thenReturn(Collections.emptyList()); + when(firewallRulesDao.listByNetworkAndPurpose(NETWORK_ID, Purpose.StaticNat)).thenReturn(Collections.emptyList()); + when(firewallRulesDao.listByNetworkPurposeTrafficType(NETWORK_ID, Purpose.Firewall, FirewallRule.TrafficType.Ingress)).thenReturn(Collections.emptyList()); + when(firewallRulesDao.listByNetworkPurposeTrafficType(NETWORK_ID, Purpose.Firewall, FirewallRule.TrafficType.Egress)).thenReturn(Collections.emptyList()); + } + + @Test + public void cleanupNetworkResourcesReturnsFalseWhenBgpCleanupFails() { + when(routedIpv4Manager.removeBgpPeersFromNetwork(network)).thenReturn(null); + + boolean result = service.cleanupNetworkResources(NETWORK_ID, caller, CALLER_USER_ID); + + assertFalse(result); + verify(networkOfferingDao).findById(OFFERING_ID); + } + + @Test + public void cleanupNetworkResourcesReturnsFalseWhenRuleCleanupThrows() throws ResourceUnavailableException { + when(rulesManager.revokeAllPFStaticNatRulesForNetwork(NETWORK_ID, CALLER_USER_ID, caller)).thenThrow(resourceUnavailable()); + + assertFalse(service.cleanupNetworkResources(NETWORK_ID, caller, CALLER_USER_ID)); + } + + @Test + public void cleanupNetworkResourcesReleasesPortableAndVpcIps() { + IPAddressVO portableIp = mock(IPAddressVO.class); + IPAddressVO vpcIp = mock(IPAddressVO.class); + when(portableIp.getVpcId()).thenReturn(null); + when(portableIp.isPortable()).thenReturn(true); + when(portableIp.getId()).thenReturn(11L); + when(vpcIp.getVpcId()).thenReturn(22L); + when(ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Arrays.asList(portableIp, vpcIp)); + + assertTrue(service.cleanupNetworkResources(NETWORK_ID, caller, CALLER_USER_ID)); + + verify(portableIp).setAssociatedWithNetworkId(null); + verify(ipAddressDao).update(11L, portableIp); + verify(vpcManager).unassignIPFromVpcNetwork(vpcIp, network); + verify(annotationDao).removeByEntityType(AnnotationService.EntityType.NETWORK.name(), NETWORK_UUID); + } + + @Test + public void cleanupNetworkResourcesMarksNonPortableIpUnavailable() throws ResourceUnavailableException { + IPAddressVO ipToRelease = mock(IPAddressVO.class); + IPAddressVO unavailableIp = mock(IPAddressVO.class); + when(ipToRelease.getVpcId()).thenReturn(null); + when(ipToRelease.isPortable()).thenReturn(false); + when(ipToRelease.getId()).thenReturn(33L); + when(ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Collections.singletonList(ipToRelease)); + when(ipAddressManager.markIpAsUnavailable(33L)).thenReturn(unavailableIp); + + assertTrue(service.cleanupNetworkResources(NETWORK_ID, caller, CALLER_USER_ID)); + + verify(ipAddressManager).markIpAsUnavailable(33L); + verify(ipAddressManager).applyIpAssociations(network, true); + } + + @Test(expected = CloudRuntimeException.class) + public void cleanupNetworkResourcesThrowsCloudRuntimeExceptionWhenIpAssociationUnexpectedlyFails() throws ResourceUnavailableException { + when(ipAddressManager.applyIpAssociations(network, true)).thenThrow(resourceUnavailable()); + + service.cleanupNetworkResources(NETWORK_ID, caller, CALLER_USER_ID); + } + + @Test + public void shutdownNetworkResourcesMarksPortForwardingRulesRevokedAndAppliesThem() throws ResourceUnavailableException { + PortForwardingRuleVO rule = mock(PortForwardingRuleVO.class); + List rules = Collections.singletonList(rule); + when(portForwardingRulesDao.listByNetwork(NETWORK_ID)).thenReturn(rules); + + assertTrue(service.shutdownNetworkResources(network, caller, CALLER_USER_ID)); + + verify(rule).setState(FirewallRule.State.Revoke); + verify(firewallManager).applyRules(rules, true, false); + } + + @Test + public void shutdownNetworkResourcesBuildsStaticNatRulesFromValidStaticNatIps() throws ResourceUnavailableException { + FirewallRuleVO staticNatRule = mock(FirewallRuleVO.class); + FirewallRuleVO ruleVO = mockStaticNatRuleVO(); + IPAddressVO ip = mock(IPAddressVO.class); + when(staticNatRule.getId()).thenReturn(44L); + when(staticNatRule.getSourceIpAddressId()).thenReturn(55L); + when(firewallRulesDao.listByNetworkAndPurpose(NETWORK_ID, Purpose.StaticNat)).thenReturn(Collections.singletonList(staticNatRule)); + when(firewallRulesDao.findById(44L)).thenReturn(ruleVO); + when(ipAddressDao.findById(55L)).thenReturn(ip); + when(ip.isOneToOneNat()).thenReturn(true); + when(ip.getAssociatedWithVmId()).thenReturn(66L); + when(ip.getVmIp()).thenReturn("10.1.1.8"); + ArgumentCaptor rulesCaptor = ArgumentCaptor.forClass(List.class); + + assertTrue(service.shutdownNetworkResources(network, caller, CALLER_USER_ID)); + + verify(ruleVO).setState(FirewallRule.State.Revoke); + verify(firewallManager, times(4)).applyRules(rulesCaptor.capture(), eq(true), eq(false)); + StaticNatRule appliedRule = (StaticNatRule) rulesCaptor.getAllValues().get(1).get(0); + assertEquals("10.1.1.8", appliedRule.getDestIpAddress()); + } + + @Test(expected = InvalidParameterValueException.class) + public void shutdownNetworkResourcesThrowsWhenStaticNatIpIsInvalid() { + FirewallRuleVO staticNatRule = mock(FirewallRuleVO.class); + IPAddressVO ip = mock(IPAddressVO.class); + when(staticNatRule.getId()).thenReturn(44L); + when(staticNatRule.getSourceIpAddressId()).thenReturn(55L); + when(firewallRulesDao.listByNetworkAndPurpose(NETWORK_ID, Purpose.StaticNat)).thenReturn(Collections.singletonList(staticNatRule)); + when(ipAddressDao.findById(55L)).thenReturn(ip); + when(ip.isOneToOneNat()).thenReturn(false); + + service.shutdownNetworkResources(network, caller, CALLER_USER_ID); + } + + @Test + public void shutdownNetworkResourcesAppliesDefaultEgressRuleWhenFirewallServiceIsSupported() throws ResourceUnavailableException { + DataCenterVO zone = mock(DataCenterVO.class); + when(dataCenterDao.findById(ZONE_ID)).thenReturn(zone); + when(zone.getNetworkType()).thenReturn(NetworkType.Advanced); + when(networkModel.areServicesSupportedInNetwork(NETWORK_ID, Service.Firewall)).thenReturn(true); + when(networkModel.getNetworkEgressDefaultPolicy(NETWORK_ID)).thenReturn(false); + + assertTrue(service.shutdownNetworkResources(network, caller, CALLER_USER_ID)); + + verify(firewallManager).applyDefaultEgressFirewallRule(NETWORK_ID, false, false); + } + + @Test + public void shutdownNetworkResourcesReturnsFalseWhenBackendCleanupReportsFailures() throws ResourceUnavailableException { + when(loadBalancingRulesManager.revokeLoadBalancersForNetwork(network, Scheme.Public)).thenReturn(false); + when(networkACLManager.revokeACLItemsForNetwork(NETWORK_ID)).thenReturn(false); + when(network.getVpcId()).thenReturn(77L); + when(rulesManager.applyStaticNatForNetwork(network, false, caller, true)).thenReturn(false); + when(ipAddressManager.applyIpAssociations(eq(network), eq(true), eq(true), anyList())).thenReturn(false); + + assertFalse(service.shutdownNetworkResources(network, caller, CALLER_USER_ID)); + } + + @Test + public void shutdownNetworkResourcesBuildsPublicIpsForRelease() throws ResourceUnavailableException { + IPAddressVO userIp = mock(IPAddressVO.class); + VlanVO vlan = mock(VlanVO.class); + when(userIp.getVlanId()).thenReturn(VLAN_ID); + when(userIp.getDataCenterId()).thenReturn(ZONE_ID); + when(userIp.getMacAddress()).thenReturn(1L); + when(ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Collections.singletonList(userIp)); + when(vlanDao.findById(VLAN_ID)).thenReturn(vlan); + ArgumentCaptor publicIpsCaptor = ArgumentCaptor.forClass(List.class); + + assertTrue(service.shutdownNetworkResources(network, caller, CALLER_USER_ID)); + + verify(userIp).setState(IpAddress.State.Releasing); + verify(ipAddressManager).applyIpAssociations(eq(network), eq(true), eq(true), publicIpsCaptor.capture()); + assertEquals(1, publicIpsCaptor.getValue().size()); + assertSame(userIp, ((PublicIp) publicIpsCaptor.getValue().get(0)).ip()); + } + + @Test(expected = CloudRuntimeException.class) + public void shutdownNetworkResourcesThrowsCloudRuntimeExceptionWhenIpAssociationUnexpectedlyFails() throws ResourceUnavailableException { + when(ipAddressManager.applyIpAssociations(eq(network), eq(true), eq(true), anyList())).thenThrow(resourceUnavailable()); + + service.shutdownNetworkResources(network, caller, CALLER_USER_ID); + } + + private FirewallRuleVO mockStaticNatRuleVO() { + FirewallRuleVO ruleVO = mock(FirewallRuleVO.class); + when(ruleVO.getId()).thenReturn(44L); + when(ruleVO.getXid()).thenReturn("xid"); + when(ruleVO.getUuid()).thenReturn("uuid"); + when(ruleVO.getProtocol()).thenReturn("tcp"); + when(ruleVO.getSourcePortStart()).thenReturn(1); + when(ruleVO.getSourcePortEnd()).thenReturn(65535); + when(ruleVO.getAccountId()).thenReturn(88L); + when(ruleVO.getDomainId()).thenReturn(99L); + when(ruleVO.getNetworkId()).thenReturn(NETWORK_ID); + when(ruleVO.getSourceIpAddressId()).thenReturn(55L); + when(ruleVO.isDisplay()).thenReturn(true); + return ruleVO; + } + + private ResourceUnavailableException resourceUnavailable() { + return new ResourceUnavailableException("unavailable", Network.class, NETWORK_ID); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingServiceImplTest.java new file mode 100644 index 000000000000..f58b4c815963 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkRuleReprogrammingServiceImplTest.java @@ -0,0 +1,314 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; + +import com.cloud.bgp.BGPService; +import com.cloud.dc.DataCenter.NetworkType; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.RemoteAccessVpn; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.FirewallRule; +import com.cloud.network.rules.FirewallRule.Purpose; +import com.cloud.network.rules.FirewallRuleVO; +import com.cloud.network.rules.LoadBalancerContainer.Scheme; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.vpc.NetworkACLManager; +import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; + +public class NetworkRuleReprogrammingServiceImplTest { + private static final long NETWORK_ID = 101L; + private static final long OFFERING_ID = 202L; + private static final long ZONE_ID = 303L; + private static final long VPN_SERVER_ADDRESS_ID = 404L; + + private NetworkRuleReprogrammingServiceImpl service; + private NetworkModel networkModel; + private NetworkOfferingDao networkOfferingDao; + private DataCenterDao dataCenterDao; + private FirewallRulesDao firewallRulesDao; + private FirewallManager firewallManager; + private IpAddressManager ipAddressManager; + private BGPService bgpService; + private RulesManager rulesManager; + private LoadBalancingRulesManager lbManager; + private RemoteAccessVpnService vpnManager; + private NetworkACLManager networkACLManager; + private Network network; + private Account caller; + private NetworkOfferingVO offering; + private DataCenterVO zone; + private FirewallRuleVO egressRule; + private FirewallRuleVO ingressRule; + private RemoteAccessVpn vpn; + + @Before + public void setUp() throws ResourceUnavailableException { + service = new NetworkRuleReprogrammingServiceImpl(); + networkModel = mock(NetworkModel.class); + networkOfferingDao = mock(NetworkOfferingDao.class); + dataCenterDao = mock(DataCenterDao.class); + firewallRulesDao = mock(FirewallRulesDao.class); + firewallManager = mock(FirewallManager.class); + ipAddressManager = mock(IpAddressManager.class); + bgpService = mock(BGPService.class); + rulesManager = mock(RulesManager.class); + lbManager = mock(LoadBalancingRulesManager.class); + vpnManager = mock(RemoteAccessVpnService.class); + networkACLManager = mock(NetworkACLManager.class); + network = mock(Network.class); + caller = mock(Account.class); + offering = mock(NetworkOfferingVO.class); + zone = mock(DataCenterVO.class); + egressRule = mock(FirewallRuleVO.class); + ingressRule = mock(FirewallRuleVO.class); + vpn = mock(RemoteAccessVpn.class); + + service.networkModel = networkModel; + service.networkOfferingDao = networkOfferingDao; + service.dataCenterDao = dataCenterDao; + service.firewallRulesDao = firewallRulesDao; + service.firewallManager = firewallManager; + service.ipAddressManager = ipAddressManager; + service.bgpService = bgpService; + service.rulesManager = rulesManager; + service.lbManager = lbManager; + service.vpnManager = vpnManager; + service.networkACLManager = networkACLManager; + + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + when(network.getDataCenterId()).thenReturn(ZONE_ID); + when(network.getGuestType()).thenReturn(Network.GuestType.Isolated); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(dataCenterDao.findById(ZONE_ID)).thenReturn(zone); + when(zone.getNetworkType()).thenReturn(NetworkType.Advanced); + when(offering.isEgressDefaultPolicy()).thenReturn(true); + when(networkModel.areServicesSupportedInNetwork(NETWORK_ID, Service.Firewall)).thenReturn(true); + when(firewallRulesDao.listByNetworkPurposeTrafficType(NETWORK_ID, Purpose.Firewall, FirewallRule.TrafficType.Egress)) + .thenReturn(Collections.singletonList(egressRule)); + when(firewallRulesDao.listByNetworkPurposeTrafficType(NETWORK_ID, Purpose.Firewall, FirewallRule.TrafficType.Ingress)) + .thenReturn(Collections.singletonList(ingressRule)); + when(firewallManager.applyFirewallRules(Collections.singletonList(egressRule), false, caller)).thenReturn(true); + when(firewallManager.applyFirewallRules(Collections.singletonList(ingressRule), false, caller)).thenReturn(true); + when(ipAddressManager.applyIpAssociations(network, false)).thenReturn(true); + when(bgpService.applyBgpPeers(network, false)).thenReturn(true); + when(rulesManager.applyStaticNatsForNetwork(network, false, caller)).thenReturn(true); + when(rulesManager.applyPortForwardingRulesForNetwork(NETWORK_ID, false, caller)).thenReturn(true); + when(rulesManager.applyStaticNatRulesForNetwork(NETWORK_ID, false, caller)).thenReturn(true); + when(lbManager.applyLoadBalancersForNetwork(network, Scheme.Public)).thenReturn(true); + when(lbManager.applyLoadBalancersForNetwork(network, Scheme.Internal)).thenReturn(true); + doReturn(Collections.singletonList(vpn)).when(vpnManager).listRemoteAccessVpns(NETWORK_ID); + when(vpn.getServerAddressId()).thenReturn(VPN_SERVER_ADDRESS_ID); + when(vpnManager.startRemoteAccessVpn(VPN_SERVER_ADDRESS_ID, false)).thenReturn(vpn); + when(networkACLManager.applyACLToNetwork(NETWORK_ID)).thenReturn(true); + } + + @Test + public void reprogramNetworkRulesAppliesDefaultEgressRuleForIsolatedFirewallNetworkAndReturnsTrue() throws ResourceUnavailableException { + assertTrue(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(firewallManager).applyDefaultEgressFirewallRule(NETWORK_ID, true, true); + verify(firewallManager).applyFirewallRules(Collections.singletonList(egressRule), false, caller); + verify(ipAddressManager).applyIpAssociations(network, false); + verify(bgpService).applyBgpPeers(network, false); + verify(rulesManager).applyStaticNatsForNetwork(network, false, caller); + verify(firewallManager).applyFirewallRules(Collections.singletonList(ingressRule), false, caller); + verify(rulesManager).applyPortForwardingRulesForNetwork(NETWORK_ID, false, caller); + verify(rulesManager).applyStaticNatRulesForNetwork(NETWORK_ID, false, caller); + verify(lbManager).applyLoadBalancersForNetwork(network, Scheme.Public); + verify(lbManager).applyLoadBalancersForNetwork(network, Scheme.Internal); + verify(vpnManager).startRemoteAccessVpn(VPN_SERVER_ADDRESS_ID, false); + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesAppliesDefaultEgressRuleForSharedAdvancedFirewallNetwork() throws ResourceUnavailableException { + when(network.getGuestType()).thenReturn(Network.GuestType.Shared); + when(zone.getNetworkType()).thenReturn(NetworkType.Advanced); + + assertTrue(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(firewallManager).applyDefaultEgressFirewallRule(NETWORK_ID, true, true); + } + + @Test + public void reprogramNetworkRulesDoesNotApplyDefaultEgressRuleForSharedBasicFirewallNetwork() throws ResourceUnavailableException { + when(network.getGuestType()).thenReturn(Network.GuestType.Shared); + when(zone.getNetworkType()).thenReturn(NetworkType.Basic); + + assertTrue(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(firewallManager, never()).applyDefaultEgressFirewallRule(NETWORK_ID, true, true); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenEgressFirewallApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(firewallManager.applyFirewallRules(Collections.singletonList(egressRule), false, caller)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenIpAssociationFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(ipAddressManager.applyIpAssociations(network, false)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenBgpApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(bgpService.applyBgpPeers(network, false)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenStaticNatApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(rulesManager.applyStaticNatsForNetwork(network, false, caller)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenIngressFirewallApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(firewallManager.applyFirewallRules(Collections.singletonList(ingressRule), false, caller)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenPortForwardingApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(rulesManager.applyPortForwardingRulesForNetwork(NETWORK_ID, false, caller)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenStaticNatRulesApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(rulesManager.applyStaticNatRulesForNetwork(NETWORK_ID, false, caller)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenPublicLoadBalancerApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(lbManager.applyLoadBalancersForNetwork(network, Scheme.Public)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenInternalLoadBalancerApplyFailsAndStillAppliesAcl() throws ResourceUnavailableException { + when(lbManager.applyLoadBalancersForNetwork(network, Scheme.Internal)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenRemoteAccessVpnStartReturnsNullAndStillAppliesAcl() throws ResourceUnavailableException { + when(vpnManager.startRemoteAccessVpn(VPN_SERVER_ADDRESS_ID, false)).thenReturn(null); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesReturnsFalseWhenNetworkAclApplyFails() throws ResourceUnavailableException { + when(networkACLManager.applyACLToNetwork(NETWORK_ID)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + } + + @Test + public void reprogramNetworkRulesAggregatesMultipleFailuresAndStillRunsLaterCollaborators() throws ResourceUnavailableException { + when(firewallManager.applyFirewallRules(Collections.singletonList(egressRule), false, caller)).thenReturn(false); + when(ipAddressManager.applyIpAssociations(network, false)).thenReturn(false); + when(rulesManager.applyPortForwardingRulesForNetwork(NETWORK_ID, false, caller)).thenReturn(false); + + assertFalse(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(networkACLManager).applyACLToNetwork(NETWORK_ID); + } + + @Test + public void reprogramNetworkRulesToleratesNullRemoteAccessVpnList() throws ResourceUnavailableException { + when(vpnManager.listRemoteAccessVpns(NETWORK_ID)).thenReturn(null); + + assertTrue(service.reprogramNetworkRules(NETWORK_ID, caller, network)); + + verify(vpnManager, never()).startRemoteAccessVpn(VPN_SERVER_ADDRESS_ID, false); + } + + @Test(expected = ResourceUnavailableException.class) + public void reprogramNetworkRulesPropagatesResourceUnavailableFromIpAssociations() throws ResourceUnavailableException { + when(ipAddressManager.applyIpAssociations(network, false)) + .thenThrow(new ResourceUnavailableException("ip apply failed", Network.class, NETWORK_ID)); + + service.reprogramNetworkRules(NETWORK_ID, caller, network); + } + + @Test(expected = ResourceUnavailableException.class) + public void reprogramNetworkRulesPropagatesResourceUnavailableFromRemoteAccessVpnStart() throws ResourceUnavailableException { + when(vpnManager.startRemoteAccessVpn(VPN_SERVER_ADDRESS_ID, false)) + .thenThrow(new ResourceUnavailableException("vpn failed", Network.class, NETWORK_ID)); + + service.reprogramNetworkRules(NETWORK_ID, caller, network); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupServiceImplTest.java new file mode 100644 index 000000000000..849756485ebb --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkServiceChangeCleanupServiceImplTest.java @@ -0,0 +1,232 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.Network.Service; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkServiceMapVO; +import com.cloud.network.dao.RemoteAccessVpnDao; +import com.cloud.network.dao.RemoteAccessVpnVO; +import com.cloud.network.lb.LoadBalancingRulesManager; +import com.cloud.network.rules.FirewallManager; +import com.cloud.network.rules.RulesManager; +import com.cloud.network.vpn.RemoteAccessVpnService; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; +import com.cloud.user.Account; +import com.cloud.user.AccountVO; +import com.cloud.user.User; +import com.cloud.user.dao.AccountDao; + +public class NetworkServiceChangeCleanupServiceImplTest { + private static final long NETWORK_ID = 101L; + private static final long ACCOUNT_ID = 202L; + private static final long OFFERING_ID = 303L; + private static final long VPN_SERVER_IP_ID = 404L; + + private NetworkServiceChangeCleanupServiceImpl service; + private NetworkOfferingDao networkOfferingDao; + private NetworkOfferingServiceMapDao networkOfferingServiceMapDao; + private NetworkServiceMapDao networkServiceMapDao; + private AccountDao accountDao; + private IPAddressDao ipAddressDao; + private RulesManager rulesManager; + private LoadBalancingRulesManager lbManager; + private FirewallManager firewallManager; + private RemoteAccessVpnDao remoteAccessVpnDao; + private RemoteAccessVpnService vpnManager; + private Network network; + private AccountVO systemAccount; + + @Before + public void setUp() { + service = new NetworkServiceChangeCleanupServiceImpl(); + networkOfferingDao = mock(NetworkOfferingDao.class); + networkOfferingServiceMapDao = mock(NetworkOfferingServiceMapDao.class); + networkServiceMapDao = mock(NetworkServiceMapDao.class); + accountDao = mock(AccountDao.class); + ipAddressDao = mock(IPAddressDao.class); + rulesManager = mock(RulesManager.class); + lbManager = mock(LoadBalancingRulesManager.class); + firewallManager = mock(FirewallManager.class); + remoteAccessVpnDao = mock(RemoteAccessVpnDao.class); + vpnManager = mock(RemoteAccessVpnService.class); + network = mock(Network.class); + systemAccount = mock(AccountVO.class); + + service.networkOfferingDao = networkOfferingDao; + service.networkOfferingServiceMapDao = networkOfferingServiceMapDao; + service.networkServiceMapDao = networkServiceMapDao; + service.accountDao = accountDao; + service.ipAddressDao = ipAddressDao; + service.rulesManager = rulesManager; + service.lbManager = lbManager; + service.firewallManager = firewallManager; + service.remoteAccessVpnDao = remoteAccessVpnDao; + service.vpnManager = vpnManager; + + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getAccountId()).thenReturn(ACCOUNT_ID); + when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + when(accountDao.findById(Account.ACCOUNT_ID_SYSTEM)).thenReturn(systemAccount); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(mock(NetworkOfferingVO.class)); + } + + @Test + public void getServicesNotSupportedInNewOfferingReturnsNetworkServicesMissingFromOffering() { + NetworkOfferingVO offering = mock(NetworkOfferingVO.class); + List networkServices = Arrays.asList(serviceMap(Service.Dhcp), serviceMap(Service.StaticNat), serviceMap(Service.Lb)); + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(networkOfferingServiceMapDao.listServicesForNetworkOffering(OFFERING_ID)) + .thenReturn(Arrays.asList(Service.Dhcp.getName(), Service.Dns.getName())); + when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(networkServices); + + List result = service.getServicesNotSupportedInNewOffering(network, OFFERING_ID); + + assertEquals(Arrays.asList(Service.StaticNat.getName(), Service.Lb.getName()), result); + } + + @Test + public void getServicesNotSupportedInNewOfferingIgnoresGatewayService() { + NetworkOfferingVO offering = mock(NetworkOfferingVO.class); + List networkServices = Arrays.asList(serviceMap(Service.Gateway), serviceMap(Service.Firewall)); + when(offering.getId()).thenReturn(OFFERING_ID); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(networkOfferingServiceMapDao.listServicesForNetworkOffering(OFFERING_ID)).thenReturn(Collections.emptyList()); + when(networkServiceMapDao.getServicesInNetwork(NETWORK_ID)).thenReturn(networkServices); + + List result = service.getServicesNotSupportedInNewOffering(network, OFFERING_ID); + + assertEquals(Collections.singletonList(Service.Firewall.getName()), result); + } + + @Test + public void cleanupConfigForStaticNatRevokesRulesAndClearsStaticNatIpState() throws ResourceUnavailableException { + IPAddressVO ip = mock(IPAddressVO.class); + when(ip.getId()).thenReturn(77L); + when(rulesManager.revokeAllPFStaticNatRulesForNetwork(NETWORK_ID, User.UID_SYSTEM, systemAccount)).thenReturn(true); + when(ipAddressDao.listStaticNatPublicIps(NETWORK_ID)).thenReturn(Collections.singletonList(ip)); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.StaticNat.getName()), network); + + verify(rulesManager).revokeAllPFStaticNatRulesForNetwork(NETWORK_ID, User.UID_SYSTEM, systemAccount); + verify(ip).setOneToOneNat(false); + verify(ip).setAssociatedWithVmId(null); + verify(ip).setVmIp(null); + verify(ip).setForRouter(false); + verify(ipAddressDao).update(77L, ip); + } + + @Test + public void cleanupConfigForPortForwardingOnlyRevokesPfStaticNatRules() throws ResourceUnavailableException { + when(rulesManager.revokeAllPFStaticNatRulesForNetwork(NETWORK_ID, User.UID_SYSTEM, systemAccount)).thenReturn(true); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.PortForwarding.getName()), network); + + verify(rulesManager).revokeAllPFStaticNatRulesForNetwork(NETWORK_ID, User.UID_SYSTEM, systemAccount); + verify(ipAddressDao, never()).listStaticNatPublicIps(NETWORK_ID); + } + + @Test + public void cleanupConfigForSourceNatClearsSourceNatFlagOnAssociatedIps() { + IPAddressVO ip = mock(IPAddressVO.class); + when(ip.getId()).thenReturn(88L); + when(ipAddressDao.listByAssociatedNetwork(NETWORK_ID, true)).thenReturn(Collections.singletonList(ip)); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.SourceNat.getName()), network); + + verify(ip).setSourceNat(false); + verify(ipAddressDao).update(88L, ip); + } + + @Test + public void cleanupConfigForLbRemovesAllLoadBalancersForNetwork() { + when(lbManager.removeAllLoadBalanacersForNetwork(NETWORK_ID, systemAccount, User.UID_SYSTEM)).thenReturn(true); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.Lb.getName()), network); + + verify(lbManager).removeAllLoadBalanacersForNetwork(NETWORK_ID, systemAccount, User.UID_SYSTEM); + } + + @Test + public void cleanupConfigForFirewallRevokesFirewallRules() throws ResourceUnavailableException { + when(firewallManager.revokeAllFirewallRulesForNetwork(network, User.UID_SYSTEM, systemAccount)).thenReturn(true); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.Firewall.getName()), network); + + verify(firewallManager).revokeAllFirewallRulesForNetwork(network, User.UID_SYSTEM, systemAccount); + } + + @Test + public void cleanupConfigForFirewallToleratesResourceUnavailableException() throws ResourceUnavailableException { + when(firewallManager.revokeAllFirewallRulesForNetwork(network, User.UID_SYSTEM, systemAccount)) + .thenThrow(new ResourceUnavailableException("firewall unavailable", Network.class, NETWORK_ID)); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.Firewall.getName()), network); + + verify(firewallManager).revokeAllFirewallRulesForNetwork(network, User.UID_SYSTEM, systemAccount); + } + + @Test + public void cleanupConfigForVpnDestroysRemoteAccessVpnForNonVpcNetwork() throws ResourceUnavailableException { + RemoteAccessVpnVO vpn = mock(RemoteAccessVpnVO.class); + when(network.getVpcId()).thenReturn(null); + when(remoteAccessVpnDao.findByAccountAndNetwork(ACCOUNT_ID, NETWORK_ID)).thenReturn(vpn); + when(vpn.getServerAddressId()).thenReturn(VPN_SERVER_IP_ID); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.Vpn.getName()), network); + + verify(vpnManager).destroyRemoteAccessVpnForIp(VPN_SERVER_IP_ID, systemAccount, true); + } + + @Test + public void cleanupConfigForVpnSkipsVpcNetworks() throws ResourceUnavailableException { + when(network.getVpcId()).thenReturn(55L); + + service.cleanupConfigForServicesInNetwork(Collections.singletonList(Service.Vpn.getName()), network); + + verify(remoteAccessVpnDao, never()).findByAccountAndNetwork(ACCOUNT_ID, NETWORK_ID); + verify(vpnManager, never()).destroyRemoteAccessVpnForIp(anyLong(), eq(systemAccount), eq(true)); + } + + private NetworkServiceMapVO serviceMap(Service serviceName) { + NetworkServiceMapVO serviceMap = mock(NetworkServiceMapVO.class); + when(serviceMap.getService()).thenReturn(serviceName.getName()); + return serviceMap; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceServiceImplTest.java new file mode 100644 index 000000000000..7f804a8a2765 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkUpdateSequenceServiceImplTest.java @@ -0,0 +1,194 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.RedundantResource; +import com.cloud.network.router.VirtualRouter; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.dao.DomainRouterDao; + +public class NetworkUpdateSequenceServiceImplTest { + + private static final long NETWORK_ID = 11L; + + private NetworkUpdateSequenceServiceImpl service; + private Network network; + private NetworkServiceMapDao networkServiceMapDao; + private DomainRouterDao routerDao; + + @Before + public void setUp() { + service = new NetworkUpdateSequenceServiceImpl(); + networkServiceMapDao = mock(NetworkServiceMapDao.class); + routerDao = mock(DomainRouterDao.class); + network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + service.networkServiceMapDao = networkServiceMapDao; + service.routerDao = routerDao; + service.networkElements = Collections.emptyList(); + } + + @Test + public void canUpdateInSequenceAllowsVirtualRouterProviderAndKnownRouterState() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + DomainRouterVO router = mockRouter(VirtualRouter.RedundantState.PRIMARY, "r-1"); + when(routerDao.listByNetworkAndRole(NETWORK_ID, VirtualRouter.Role.VIRTUAL_ROUTER)).thenReturn(Collections.singletonList(router)); + + assertTrue(service.canUpdateInSequence(network, false)); + } + + @Test + public void canUpdateInSequenceRejectsNonVirtualRouterProvider() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Arrays.asList(Provider.VirtualRouter.getName(), Provider.Netscaler.getName())); + + try { + service.canUpdateInSequence(network, false); + fail("Expected UnsupportedOperationException"); + } catch (UnsupportedOperationException e) { + assertTrue(e.getMessage().contains("providers other than virtualrouter")); + } + } + + @Test + public void canUpdateInSequenceRejectsUnknownRedundantStateWithoutForced() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + DomainRouterVO router = mockRouter(VirtualRouter.RedundantState.UNKNOWN, "r-unknown"); + when(routerDao.listByNetworkAndRole(NETWORK_ID, VirtualRouter.Role.VIRTUAL_ROUTER)).thenReturn(Collections.singletonList(router)); + + try { + service.canUpdateInSequence(network, false); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("r-unknown")); + assertTrue(e.getMessage().contains("forced to true")); + } + } + + @Test + public void canUpdateInSequenceAllowsUnknownRedundantStateWhenForced() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + DomainRouterVO router = mockRouter(VirtualRouter.RedundantState.UNKNOWN, "r-forced"); + when(routerDao.listByNetworkAndRole(NETWORK_ID, VirtualRouter.Role.VIRTUAL_ROUTER)).thenReturn(Collections.singletonList(router)); + + assertTrue(service.canUpdateInSequence(network, true)); + } + + @Test + public void configureUpdateInSequenceDispatchesOnlyMatchingRedundantResource() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + NetworkElement matching = redundantElement(Provider.VirtualRouter, 0); + NetworkElement nonMatching = redundantElement(Provider.Netscaler, 0); + NetworkElement nonRedundant = mock(NetworkElement.class); + when(nonRedundant.getProvider()).thenReturn(Provider.VirtualRouter); + service.networkElements = Arrays.asList(nonMatching, nonRedundant, matching); + + service.configureUpdateInSequence(network); + + verify((RedundantResource) matching).configureResource(network); + verify((RedundantResource) nonMatching, never()).configureResource(network); + } + + @Test + public void getResourceCountReturnsFirstMatchingRedundantResourceCount() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + NetworkElement first = redundantElement(Provider.VirtualRouter, 3); + NetworkElement second = redundantElement(Provider.VirtualRouter, 7); + service.networkElements = Arrays.asList(first, second); + + assertEquals(3, service.getResourceCount(network)); + verify((RedundantResource) second, never()).getResourceCount(network); + } + + @Test + public void getResourceCountReturnsZeroWhenNoMatchingRedundantResourceExists() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + NetworkElement nonRedundant = mock(NetworkElement.class); + when(nonRedundant.getProvider()).thenReturn(Provider.VirtualRouter); + service.networkElements = Collections.singletonList(nonRedundant); + + assertEquals(0, service.getResourceCount(network)); + } + + @Test + public void finalizeUpdateInSequenceDispatchesFirstMatchingRedundantResource() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + NetworkElement first = redundantElement(Provider.VirtualRouter, 1); + NetworkElement second = redundantElement(Provider.VirtualRouter, 2); + service.networkElements = Arrays.asList(first, second); + + service.finalizeUpdateInSequence(network, true); + + verify((RedundantResource) first).finalize(network, true); + verify((RedundantResource) second, never()).finalize(network, true); + } + + @Test + public void finalizeUpdateInSequenceIgnoresNonMatchingProvider() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.singletonList(Provider.VirtualRouter.getName())); + NetworkElement nonMatching = redundantElement(Provider.Netscaler, 1); + service.networkElements = Collections.singletonList(nonMatching); + + service.finalizeUpdateInSequence(network, false); + + verify((RedundantResource) nonMatching, never()).finalize(network, false); + } + + @Test + public void configureUpdateInSequenceDoesNothingWhenProviderListIsEmpty() { + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(Collections.emptyList()); + NetworkElement element = redundantElement(Provider.VirtualRouter, 1); + service.networkElements = Collections.singletonList(element); + + service.configureUpdateInSequence(network); + + verify((RedundantResource) element, never()).configureResource(network); + } + + private DomainRouterVO mockRouter(VirtualRouter.RedundantState state, String instanceName) { + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getRedundantState()).thenReturn(state); + when(router.getInstanceName()).thenReturn(instanceName); + return router; + } + + private NetworkElement redundantElement(Provider provider, int resourceCount) { + NetworkElement element = mock(NetworkElement.class, Mockito.withSettings().extraInterfaces(RedundantResource.class)); + when(element.getProvider()).thenReturn(provider); + when(((RedundantResource) element).getResourceCount(network)).thenReturn(resourceCount); + return element; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupServiceImplTest.java new file mode 100644 index 000000000000..c4e5e8c8a060 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkVlanRangeCleanupServiceImplTest.java @@ -0,0 +1,217 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static com.cloud.configuration.ConfigurationManager.MESSAGE_DELETE_VLAN_IP_RANGE_EVENT; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.apache.cloudstack.framework.messagebus.PublishScope; +import org.junit.Before; +import org.junit.Test; + +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.VlanDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.dao.PrivateIpDao; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.offerings.dao.NetworkOfferingDao; +import com.cloud.user.Account; +import com.cloud.utils.Pair; + +public class NetworkVlanRangeCleanupServiceImplTest { + + private static final long NETWORK_ID = 101L; + private static final long USER_ID = 202L; + private static final long OFFERING_ID = 303L; + private static final long ZONE_ID = 404L; + private static final long PHYSICAL_NETWORK_ID = 505L; + private static final long ACCOUNT_ID = 606L; + private static final String RESERVATION_ID = "reservation-1"; + private static final String SENDER_ADDRESS = "NetworkManager"; + + private NetworkVlanRangeCleanupServiceImpl service; + private VlanDao vlanDao; + private ConfigurationManager configurationManager; + private PrivateIpDao privateIpDao; + private NetworkOfferingDao networkOfferingDao; + private DataCenterDao dataCenterDao; + private NetworkOfferingVlanValidationService networkOfferingVlanValidationService; + private MessageBus messageBus; + private NetworkVO network; + private Account caller; + private NetworkOfferingVO offering; + + @Before + public void setUp() { + service = new NetworkVlanRangeCleanupServiceImpl(); + vlanDao = mock(VlanDao.class); + configurationManager = mock(ConfigurationManager.class); + privateIpDao = mock(PrivateIpDao.class); + networkOfferingDao = mock(NetworkOfferingDao.class); + dataCenterDao = mock(DataCenterDao.class); + networkOfferingVlanValidationService = mock(NetworkOfferingVlanValidationService.class); + messageBus = mock(MessageBus.class); + network = mock(NetworkVO.class); + caller = mock(Account.class); + offering = mock(NetworkOfferingVO.class); + + service.vlanDao = vlanDao; + service.configurationManager = configurationManager; + service.privateIpDao = privateIpDao; + service.networkOfferingDao = networkOfferingDao; + service.dataCenterDao = dataCenterDao; + service.networkOfferingVlanValidationService = networkOfferingVlanValidationService; + service.messageBus = messageBus; + + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getNetworkOfferingId()).thenReturn(OFFERING_ID); + when(network.getBroadcastUri()).thenReturn(URI.create("vlan://321")); + when(network.getDataCenterId()).thenReturn(ZONE_ID); + when(network.getPhysicalNetworkId()).thenReturn(PHYSICAL_NETWORK_ID); + when(network.getAccountId()).thenReturn(ACCOUNT_ID); + when(network.getReservationId()).thenReturn(RESERVATION_ID); + when(networkOfferingDao.findById(OFFERING_ID)).thenReturn(offering); + when(vlanDao.listVlansByNetworkId(NETWORK_ID)).thenReturn(Collections.emptyList()); + when(privateIpDao.countAllocatedByNetworkId(NETWORK_ID)).thenReturn(0); + } + + @Test + public void deleteVlansInNetworkDeletesPublicVlansAndPrivateRange() { + VlanVO firstSource = vlan(1L, "source-1"); + VlanVO secondSource = vlan(2L, "source-2"); + VlanVO firstDeleted = vlan(11L, "deleted-1"); + VlanVO secondDeleted = vlan(12L, "deleted-2"); + when(vlanDao.listVlansByNetworkId(NETWORK_ID)).thenReturn(Arrays.asList(firstSource, secondSource)); + when(configurationManager.deleteVlanAndPublicIpRange(USER_ID, firstSource.getId(), caller)).thenReturn(firstDeleted); + when(configurationManager.deleteVlanAndPublicIpRange(USER_ID, secondSource.getId(), caller)).thenReturn(secondDeleted); + + Pair> result = service.deleteVlansInNetwork(network, USER_ID, caller); + + assertTrue(result.first()); + assertEquals(Arrays.asList(firstDeleted, secondDeleted), result.second()); + verify(privateIpDao).deleteByNetworkId(NETWORK_ID); + } + + @Test + public void deleteVlansInNetworkReturnsFalseWhenPublicVlanDeleteFails() { + VlanVO firstSource = vlan(1L, "source-1"); + VlanVO secondSource = vlan(2L, "source-2"); + VlanVO secondDeleted = vlan(12L, "deleted-2"); + when(vlanDao.listVlansByNetworkId(NETWORK_ID)).thenReturn(Arrays.asList(firstSource, secondSource)); + when(configurationManager.deleteVlanAndPublicIpRange(USER_ID, firstSource.getId(), caller)).thenReturn(null); + when(configurationManager.deleteVlanAndPublicIpRange(USER_ID, secondSource.getId(), caller)).thenReturn(secondDeleted); + + Pair> result = service.deleteVlansInNetwork(network, USER_ID, caller); + + assertFalse(result.first()); + assertEquals(Collections.singletonList(secondDeleted), result.second()); + verify(configurationManager).deleteVlanAndPublicIpRange(USER_ID, firstSource.getId(), caller); + verify(configurationManager).deleteVlanAndPublicIpRange(USER_ID, secondSource.getId(), caller); + } + + @Test + public void deleteVlansInNetworkDoesNotDeletePrivateRangeWhenAllocatedPrivateIpsExist() { + when(privateIpDao.countAllocatedByNetworkId(NETWORK_ID)).thenReturn(1); + + Pair> result = service.deleteVlansInNetwork(network, USER_ID, caller); + + assertFalse(result.first()); + verify(privateIpDao, never()).deleteByNetworkId(NETWORK_ID); + } + + @Test + public void deleteVlansInNetworkDeletesPrivateRangeWhenNoPrivateIpsAreAllocated() { + Pair> result = service.deleteVlansInNetwork(network, USER_ID, caller); + + assertTrue(result.first()); + verify(privateIpDao).deleteByNetworkId(NETWORK_ID); + } + + @Test + public void deleteVlansInNetworkReleasesVnetForSharedNetworkWithoutSpecifyVlan() { + when(networkOfferingVlanValidationService.isSharedNetworkWithoutSpecifyVlan(offering)).thenReturn(true); + + service.deleteVlansInNetwork(network, USER_ID, caller); + + verify(dataCenterDao).releaseVnet("321", ZONE_ID, PHYSICAL_NETWORK_ID, ACCOUNT_ID, RESERVATION_ID); + } + + @Test + public void deleteVlansInNetworkDoesNotReleaseVnetForOtherOfferings() { + when(networkOfferingVlanValidationService.isSharedNetworkWithoutSpecifyVlan(offering)).thenReturn(false); + + service.deleteVlansInNetwork(network, USER_ID, caller); + + verify(dataCenterDao, never()).releaseVnet("321", ZONE_ID, PHYSICAL_NETWORK_ID, ACCOUNT_ID, RESERVATION_ID); + } + + @Test + public void deleteVlansInNetworkReturnsDeletedListWhenNoPublicVlansExist() { + Pair> result = service.deleteVlansInNetwork(network, USER_ID, caller); + + assertTrue(result.first()); + assertTrue(result.second().isEmpty()); + verify(configurationManager, never()).deleteVlanAndPublicIpRange(USER_ID, 1L, caller); + } + + @Test + public void publishDeletedVlanRangesPublishesEachDeletedRange() { + VlanVO firstDeleted = vlan(11L, "deleted-1"); + VlanVO secondDeleted = vlan(12L, "deleted-2"); + + service.publishDeletedVlanRanges(SENDER_ADDRESS, Arrays.asList(firstDeleted, secondDeleted)); + + verify(messageBus).publish(SENDER_ADDRESS, MESSAGE_DELETE_VLAN_IP_RANGE_EVENT, PublishScope.LOCAL, firstDeleted); + verify(messageBus).publish(SENDER_ADDRESS, MESSAGE_DELETE_VLAN_IP_RANGE_EVENT, PublishScope.LOCAL, secondDeleted); + } + + @Test + public void publishDeletedVlanRangesIgnoresNullLists() { + service.publishDeletedVlanRanges(SENDER_ADDRESS, null); + + verifyNoInteractions(messageBus); + } + + @Test + public void publishDeletedVlanRangesIgnoresEmptyLists() { + service.publishDeletedVlanRanges(SENDER_ADDRESS, Collections.emptyList()); + + verifyNoInteractions(messageBus); + } + + private VlanVO vlan(long id, String uuid) { + VlanVO vlan = mock(VlanVO.class); + when(vlan.getId()).thenReturn(id); + when(vlan.getUuid()).thenReturn(uuid); + return vlan; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryServiceImplTest.java new file mode 100644 index 000000000000..663d0cea2647 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicAuxiliaryServiceImplTest.java @@ -0,0 +1,167 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; + +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.element.LoadBalancingServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.network.guru.NetworkGuruAdditionalFunctions; +import com.cloud.vm.Nic; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicSecondaryIpDao; +import com.cloud.vm.dao.NicSecondaryIpVO; + +public class NicAuxiliaryServiceImplTest { + + private static final long VM_ID = 11L; + private static final long NIC_ID = 22L; + private static final long NETWORK_ID = 33L; + private static final String GURU_NAME = "testGuru"; + + private NicAuxiliaryServiceImpl service; + private NicDao nicDao; + private NicSecondaryIpDao nicSecondaryIpDao; + private NetworkDao networkDao; + private NetworkModel networkModel; + + @Before + public void setUp() { + service = new NicAuxiliaryServiceImpl(); + nicDao = mock(NicDao.class); + nicSecondaryIpDao = mock(NicSecondaryIpDao.class); + networkDao = mock(NetworkDao.class); + networkModel = mock(NetworkModel.class); + service.nicDao = nicDao; + service.nicSecondaryIpDao = nicSecondaryIpDao; + service.networksDao = networkDao; + service.networkModel = networkModel; + } + + @Test + public void listVmNicsAddsNsxLogicalSwitchMetadataWhenProviderIsPresent() { + NicVO nic = new NicVO(GURU_NAME, VM_ID, NETWORK_ID, Type.User); + nic.setUuid("nic-uuid"); + NetworkVO network = new NetworkVO(); + network.setGuruName(GURU_NAME); + NetworkGuruAdditionalFunctions guru = mock(NetworkGuruAdditionalFunctions.class, + org.mockito.Mockito.withSettings().extraInterfaces(NetworkGuru.class)); + Map params = new HashMap<>(); + params.put(NetworkGuruAdditionalFunctions.NSX_LSWITCH_UUID, "logical-switch"); + params.put(NetworkGuruAdditionalFunctions.NSX_LSWITCHPORT_UUID, "logical-port"); + + when(((NetworkGuru) guru).getName()).thenReturn(GURU_NAME); + when(nicDao.listByVmId(VM_ID)).thenReturn(Collections.singletonList(nic)); + when(networkModel.isProviderForNetwork(Network.Provider.Nsx, NETWORK_ID)).thenReturn(true); + when(networkDao.findById(NETWORK_ID)).thenReturn(network); + org.mockito.Mockito.doReturn(params).when(guru).listAdditionalNicParams("nic-uuid"); + + List result = service.listVmNics(VM_ID, null, null, null, Collections.singletonList((NetworkGuru) guru)); + + assertEquals(1, result.size()); + assertSame(nic, result.get(0)); + assertEquals("logical-switch", nic.getNsxLogicalSwitchUuid()); + assertEquals("logical-port", nic.getNsxLogicalSwitchPortUuid()); + } + + @Test + public void removeVmSecondaryIpsOfNicDeletesAllSecondaryIpRows() { + NicSecondaryIpVO first = mock(NicSecondaryIpVO.class); + NicSecondaryIpVO second = mock(NicSecondaryIpVO.class); + when(first.getId()).thenReturn(101L); + when(second.getId()).thenReturn(102L); + when(nicSecondaryIpDao.listByNicId(NIC_ID)).thenReturn(List.of(first, second)); + + assertTrue(service.removeVmSecondaryIpsOfNic(NIC_ID)); + + verify(nicSecondaryIpDao).remove(101L); + verify(nicSecondaryIpDao).remove(102L); + } + + @Test + public void savePlaceholderNicPersistsReservedPlaceholderNic() { + Network network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(nicDao.persist(any(NicVO.class))).thenAnswer(invocation -> invocation.getArgument(0)); + + NicVO nic = service.savePlaceholderNic(network, "10.1.1.10", "2001:db8::10", + "2001:db8::/64", "2001:db8::1", "unit-test", Type.DomainRouter); + + assertEquals("10.1.1.10", nic.getIPv4Address()); + assertEquals("2001:db8::10", nic.getIPv6Address()); + assertEquals("2001:db8::/64", nic.getIPv6Cidr()); + assertEquals("2001:db8::1", nic.getIPv6Gateway()); + assertEquals(Nic.ReservationStrategy.PlaceHolder, nic.getReservationStrategy()); + assertEquals(Nic.State.Reserved, nic.getState()); + assertEquals(Type.DomainRouter, nic.getVmType()); + verify(nicDao).persist(nic); + } + + @Test + public void unmanageNicsRemovesEveryNicForStoppedVm() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + VirtualMachine vm = mock(VirtualMachine.class); + NicVO first = new NicVO(GURU_NAME, VM_ID, NETWORK_ID, Type.User); + NicVO second = new NicVO(GURU_NAME, VM_ID, NETWORK_ID, Type.User); + AtomicInteger removed = new AtomicInteger(); + + when(vmProfile.getVirtualMachine()).thenReturn(vm); + when(vmProfile.getId()).thenReturn(VM_ID); + when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + when(nicDao.listByVmId(VM_ID)).thenReturn(List.of(first, second)); + + service.unmanageNics(vmProfile, (profile, nic) -> removed.incrementAndGet()); + + assertEquals(2, removed.get()); + } + + @Test + public void expungeLbVmRefsCallsOnlyLoadBalancingElements() { + LoadBalancingServiceProvider lbProvider = mock(LoadBalancingServiceProvider.class, + org.mockito.Mockito.withSettings().extraInterfaces(NetworkElement.class)); + NetworkElement otherElement = mock(NetworkElement.class); + List vmIds = List.of(1L, 2L); + + service.expungeLbVmRefs(List.of((NetworkElement) lbProvider, otherElement), vmIds, 50L); + + verify(lbProvider).expungeLbVmRefs(vmIds, 50L); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupServiceTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupServiceTest.java new file mode 100644 index 000000000000..d6a8b3b38c06 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicDhcpCleanupServiceTest.java @@ -0,0 +1,360 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.vm.Nic; +import com.cloud.vm.NicIpAlias; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.vm.dao.NicIpAliasDao; +import com.cloud.vm.dao.NicIpAliasVO; + +@RunWith(JUnit4.class) +public class NicDhcpCleanupServiceTest { + + private NicDhcpCleanupServiceImpl service; + + private NetworkServiceMapDao networkServiceMapDao; + private NetworkModel networkModel; + private NetworkDao networksDao; + private NicDao nicDao; + private NicIpAliasDao nicIpAliasDao; + private com.cloud.network.dao.IPAddressDao publicIpAddressDao; + private NetworkProviderResolutionService networkProviderResolutionService; + + private static final long NETWORK_ID = 10L; + + @Before + public void setUp() { + service = new NicDhcpCleanupServiceImpl(); + networkServiceMapDao = mock(NetworkServiceMapDao.class); + networkModel = mock(NetworkModel.class); + networksDao = mock(NetworkDao.class); + nicDao = mock(NicDao.class); + nicIpAliasDao = mock(NicIpAliasDao.class); + publicIpAddressDao = mock(com.cloud.network.dao.IPAddressDao.class); + networkProviderResolutionService = mock(NetworkProviderResolutionService.class); + + service.networkServiceMapDao = networkServiceMapDao; + service.networkModel = networkModel; + service.networksDao = networksDao; + service.nicDao = nicDao; + service.nicIpAliasDao = nicIpAliasDao; + service.publicIpAddressDao = publicIpAddressDao; + service.networkProviderResolutionService = networkProviderResolutionService; + service.networkElements = new ArrayList<>(); + } + + // ----------------------------------------------------------------------- + // isDhcpAccrossMultipleSubnetsSupported + // ----------------------------------------------------------------------- + + @Test + public void testIsDhcpAccrossMultipleSubnetsSupportedTrue() { + DhcpServiceProvider provider = mock(DhcpServiceProvider.class); + Map caps = new HashMap<>(); + caps.put(Network.Capability.DhcpAccrossMultipleSubnets, "true"); + Map> services = new HashMap<>(); + services.put(Network.Service.Dhcp, caps); + when(provider.getCapabilities()).thenReturn(services); + + assertTrue(service.isDhcpAccrossMultipleSubnetsSupported(provider)); + } + + @Test + public void testIsDhcpAccrossMultipleSubnetsSupportedFalse() { + DhcpServiceProvider provider = mock(DhcpServiceProvider.class); + Map caps = new HashMap<>(); + caps.put(Network.Capability.DhcpAccrossMultipleSubnets, "false"); + Map> services = new HashMap<>(); + services.put(Network.Service.Dhcp, caps); + when(provider.getCapabilities()).thenReturn(services); + + assertFalse(service.isDhcpAccrossMultipleSubnetsSupported(provider)); + } + + @Test + public void testIsDhcpAccrossMultipleSubnetsSupportedCapabilityNull() { + DhcpServiceProvider provider = mock(DhcpServiceProvider.class); + Map caps = new HashMap<>(); + // DhcpAccrossMultipleSubnets not present + Map> services = new HashMap<>(); + services.put(Network.Service.Dhcp, caps); + when(provider.getCapabilities()).thenReturn(services); + + assertFalse(service.isDhcpAccrossMultipleSubnetsSupported(provider)); + } + + // ----------------------------------------------------------------------- + // isLastNicInSubnet + // ----------------------------------------------------------------------- + + @Test + public void testIsLastNicInSubnetTrueWhenSingleNic() { + NicVO nic = mock(NicVO.class); + when(nic.getNetworkId()).thenReturn(NETWORK_ID); + when(nic.getIPv4Gateway()).thenReturn("10.0.0.1"); + URI broadcastUri = URI.create("vlan://100"); + when(nic.getBroadcastUri()).thenReturn(broadcastUri); + // Only this NIC in the subnet + when(nicDao.listByNetworkIdTypeAndGatewayAndBroadcastUri(NETWORK_ID, VirtualMachine.Type.User, "10.0.0.1", broadcastUri)) + .thenReturn(List.of(nic)); + + assertTrue(service.isLastNicInSubnet(nic)); + } + + @Test + public void testIsLastNicInSubnetFalseWhenMultipleNics() { + NicVO nic = mock(NicVO.class); + NicVO nic2 = mock(NicVO.class); + when(nic.getNetworkId()).thenReturn(NETWORK_ID); + when(nic.getIPv4Gateway()).thenReturn("10.0.0.1"); + URI broadcastUri = URI.create("vlan://100"); + when(nic.getBroadcastUri()).thenReturn(broadcastUri); + // Two NICs in subnet + when(nicDao.listByNetworkIdTypeAndGatewayAndBroadcastUri(NETWORK_ID, VirtualMachine.Type.User, "10.0.0.1", broadcastUri)) + .thenReturn(List.of(nic, nic2)); + + assertFalse(service.isLastNicInSubnet(nic)); + } + + // ----------------------------------------------------------------------- + // cleanupNicDhcpDnsEntry + // ----------------------------------------------------------------------- + + @Test + public void testCleanupNicDhcpDnsEntryNoProviders() { + Network network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + NicProfile nicProfile = mock(NicProfile.class); + + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(new ArrayList<>()); + service.networkElements = new ArrayList<>(); + + // Should complete without error and do nothing + service.cleanupNicDhcpDnsEntry(network, vmProfile, nicProfile); + } + + @Test + public void testCleanupNicDhcpDnsEntrySkipsNonUserVm() throws ResourceUnavailableException { + Network network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getPhysicalNetworkId()).thenReturn(1L); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + NicProfile nicProfile = mock(NicProfile.class); + + DhcpServiceProvider dhcpElement = mock(DhcpServiceProvider.class); + Network.Provider provider = Network.Provider.VirtualRouter; + when(dhcpElement.getProvider()).thenReturn(provider); + + List elements = new ArrayList<>(); + elements.add(dhcpElement); + service.networkElements = elements; + + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(List.of("VirtualRouter")); + when(networkModel.isProviderEnabledInPhysicalNetwork(anyLong(), anyString())).thenReturn(true); + when(networkModel.getPhysicalNetworkId(network)).thenReturn(1L); + + service.cleanupNicDhcpDnsEntry(network, vmProfile, nicProfile); + + // removeDhcpEntry should NOT be called for non-User VMs + verify(dhcpElement, never()).removeDhcpEntry(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()); + } + + @Test + public void testCleanupNicDhcpDnsEntryCallsRemoveDhcpEntry() throws ResourceUnavailableException { + Network network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getPhysicalNetworkId()).thenReturn(1L); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getType()).thenReturn(VirtualMachine.Type.User); + NicProfile nicProfile = mock(NicProfile.class); + + DhcpServiceProvider dhcpElement = mock(DhcpServiceProvider.class); + Network.Provider provider = Network.Provider.VirtualRouter; + when(dhcpElement.getProvider()).thenReturn(provider); + + List elements = new ArrayList<>(); + elements.add(dhcpElement); + service.networkElements = elements; + + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(List.of("VirtualRouter")); + when(networkModel.isProviderEnabledInPhysicalNetwork(anyLong(), anyString())).thenReturn(true); + when(networkModel.getPhysicalNetworkId(network)).thenReturn(1L); + when(networkModel.areServicesSupportedInNetwork(NETWORK_ID, Network.Service.Dhcp)).thenReturn(true); + when(networkModel.isProviderSupportServiceInNetwork(NETWORK_ID, Network.Service.Dhcp, provider)).thenReturn(true); + + service.cleanupNicDhcpDnsEntry(network, vmProfile, nicProfile); + + verify(dhcpElement, Mockito.times(1)).removeDhcpEntry(network, nicProfile, vmProfile); + } + + @Test + public void testCleanupNicDhcpDnsEntrySwallowsResourceUnavailable() throws ResourceUnavailableException { + Network network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(network.getPhysicalNetworkId()).thenReturn(1L); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getType()).thenReturn(VirtualMachine.Type.User); + NicProfile nicProfile = mock(NicProfile.class); + + DhcpServiceProvider dhcpElement = mock(DhcpServiceProvider.class); + Network.Provider provider = Network.Provider.VirtualRouter; + when(dhcpElement.getProvider()).thenReturn(provider); + when(dhcpElement.removeDhcpEntry(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any())) + .thenThrow(new ResourceUnavailableException("test", Network.class, 1L)); + + List elements = new ArrayList<>(); + elements.add(dhcpElement); + service.networkElements = elements; + + when(networkServiceMapDao.getDistinctProviders(NETWORK_ID)).thenReturn(List.of("VirtualRouter")); + when(networkModel.isProviderEnabledInPhysicalNetwork(anyLong(), anyString())).thenReturn(true); + when(networkModel.getPhysicalNetworkId(network)).thenReturn(1L); + when(networkModel.areServicesSupportedInNetwork(NETWORK_ID, Network.Service.Dhcp)).thenReturn(true); + when(networkModel.isProviderSupportServiceInNetwork(NETWORK_ID, Network.Service.Dhcp, provider)).thenReturn(true); + + // Should not throw; swallows ResourceUnavailableException + service.cleanupNicDhcpDnsEntry(network, vmProfile, nicProfile); + } + + // ----------------------------------------------------------------------- + // removeDhcpServiceInSubnet + // ----------------------------------------------------------------------- + + @Test + public void testRemoveDhcpServiceInSubnetNoAlias() { + Nic nic = mock(Nic.class); + when(nic.getNetworkId()).thenReturn(NETWORK_ID); + when(nic.getIPv4Gateway()).thenReturn("10.0.0.1"); + + NetworkVO network = mock(NetworkVO.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(networksDao.findById(NETWORK_ID)).thenReturn(network); + + DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class); + when(networkProviderResolutionService.getDhcpServiceProvider(network)).thenReturn(dhcpProvider); + + when(nicIpAliasDao.findByGatewayAndNetworkIdAndState("10.0.0.1", NETWORK_ID, NicIpAlias.State.active)) + .thenReturn(null); + + // Should complete without error + service.removeDhcpServiceInSubnet(nic); + + verify(nicIpAliasDao, never()).update(anyLong(), ArgumentMatchers.any()); + } + + @Test + public void testRemoveDhcpServiceInSubnetWithAlias() throws ResourceUnavailableException { + Nic nic = mock(Nic.class); + when(nic.getNetworkId()).thenReturn(NETWORK_ID); + when(nic.getIPv4Gateway()).thenReturn("10.0.0.1"); + + NetworkVO network = mock(NetworkVO.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(networksDao.findById(NETWORK_ID)).thenReturn(network); + + DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class); + when(networkProviderResolutionService.getDhcpServiceProvider(network)).thenReturn(dhcpProvider); + when(dhcpProvider.removeDhcpSupportForSubnet(network)).thenReturn(true); + + NicIpAliasVO ipAlias = mock(NicIpAliasVO.class); + when(ipAlias.getNetworkId()).thenReturn(NETWORK_ID); + when(ipAlias.getIp4Address()).thenReturn("10.0.0.100"); + when(ipAlias.getId()).thenReturn(1L); + + when(nicIpAliasDao.findByGatewayAndNetworkIdAndState("10.0.0.1", NETWORK_ID, NicIpAlias.State.active)) + .thenReturn(ipAlias); + + IPAddressVO aliasIp = mock(IPAddressVO.class); + when(aliasIp.getId()).thenReturn(5L); + when(publicIpAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, "10.0.0.100")).thenReturn(aliasIp); + when(nicIpAliasDao.update(anyLong(), ArgumentMatchers.any())).thenReturn(true); + + service.removeDhcpServiceInSubnet(nic); + + verify(ipAlias, Mockito.times(1)).setState(NicIpAlias.State.revoked); + verify(dhcpProvider, Mockito.times(1)).removeDhcpSupportForSubnet(network); + } + + @Test + public void testRemoveDhcpServiceInSubnetSwallowsResourceUnavailable() throws ResourceUnavailableException { + Nic nic = mock(Nic.class); + when(nic.getNetworkId()).thenReturn(NETWORK_ID); + when(nic.getIPv4Gateway()).thenReturn("10.0.0.1"); + + NetworkVO network = mock(NetworkVO.class); + when(network.getId()).thenReturn(NETWORK_ID); + when(networksDao.findById(NETWORK_ID)).thenReturn(network); + + DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class); + when(networkProviderResolutionService.getDhcpServiceProvider(network)).thenReturn(dhcpProvider); + when(dhcpProvider.removeDhcpSupportForSubnet(network)).thenThrow(new ResourceUnavailableException("unreachable", Network.class, 1L)); + + NicIpAliasVO ipAlias = mock(NicIpAliasVO.class); + when(ipAlias.getNetworkId()).thenReturn(NETWORK_ID); + when(ipAlias.getIp4Address()).thenReturn("10.0.0.100"); + when(ipAlias.getId()).thenReturn(1L); + + when(nicIpAliasDao.findByGatewayAndNetworkIdAndState("10.0.0.1", NETWORK_ID, NicIpAlias.State.active)) + .thenReturn(ipAlias); + + IPAddressVO aliasIp = mock(IPAddressVO.class); + when(aliasIp.getId()).thenReturn(5L); + when(publicIpAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, "10.0.0.100")).thenReturn(aliasIp); + when(nicIpAliasDao.update(anyLong(), ArgumentMatchers.any())).thenReturn(true); + + // Should not throw + service.removeDhcpServiceInSubnet(nic); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationServiceImplTest.java new file mode 100644 index 000000000000..fc4e982d6871 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicElementPreparationServiceImplTest.java @@ -0,0 +1,173 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; + +import com.cloud.deploy.DeployDestination; +import com.cloud.network.Network; +import com.cloud.network.Network.Provider; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.element.ConfigDriveNetworkElement; +import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.DnsServiceProvider; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.element.UserDataServiceProvider; +import com.cloud.vm.NicProfile; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +public class NicElementPreparationServiceImplTest { + + private static final long NETWORK_ID = 42L; + private static final Provider PROVIDER = Provider.VirtualRouter; + + private NicElementPreparationServiceImpl service; + private NetworkModel networkModel; + private NicDhcpCleanupService nicDhcpCleanupService; + private Network network; + private NicProfile nicProfile; + private VirtualMachineProfile vmProfile; + private DeployDestination dest; + private ReservationContext context; + + @Before + public void setUp() { + networkModel = mock(NetworkModel.class); + nicDhcpCleanupService = mock(NicDhcpCleanupService.class); + network = mock(Network.class); + nicProfile = new NicProfile(); + vmProfile = mock(VirtualMachineProfile.class); + dest = mock(DeployDestination.class); + context = mock(ReservationContext.class); + + service = new NicElementPreparationServiceImpl(); + service.networkModel = networkModel; + service.nicDhcpCleanupService = nicDhcpCleanupService; + + when(network.getId()).thenReturn(NETWORK_ID); + when(vmProfile.getType()).thenReturn(VirtualMachine.Type.User); + } + + @Test + public void prepareElementAddsDhcpEntryAfterSubnetSupportWhenDhcpIsSupported() throws Exception { + DhcpServiceProvider element = mock(DhcpServiceProvider.class); + when(element.getProvider()).thenReturn(PROVIDER); + support(Service.Dhcp); + when(nicDhcpCleanupService.isDhcpAccrossMultipleSubnetsSupported(element)).thenReturn(true); + when(element.configDhcpSupportForSubnet(network, nicProfile, vmProfile, dest, context)).thenReturn(true); + when(element.addDhcpEntry(network, nicProfile, vmProfile, dest, context)).thenReturn(true); + + assertTrue(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element).prepare(network, nicProfile, vmProfile, dest, context); + verify(element).configDhcpSupportForSubnet(network, nicProfile, vmProfile, dest, context); + verify(element).addDhcpEntry(network, nicProfile, vmProfile, dest, context); + } + + @Test + public void prepareElementReturnsFalseWhenDhcpEntryFails() throws Exception { + DhcpServiceProvider element = mock(DhcpServiceProvider.class); + when(element.getProvider()).thenReturn(PROVIDER); + support(Service.Dhcp); + when(nicDhcpCleanupService.isDhcpAccrossMultipleSubnetsSupported(element)).thenReturn(false); + when(element.addDhcpEntry(network, nicProfile, vmProfile, dest, context)).thenReturn(false); + + assertFalse(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element, never()).configDhcpSupportForSubnet(network, nicProfile, vmProfile, dest, context); + } + + @Test + public void prepareElementAddsDnsEntryAndSkipsSubnetSupportForIpv6Nic() throws Exception { + DnsServiceProvider element = mock(DnsServiceProvider.class); + nicProfile.setIPv6Address("2001:db8::10"); + when(element.getProvider()).thenReturn(PROVIDER); + support(Service.Dns); + when(element.addDnsEntry(network, nicProfile, vmProfile, dest, context)).thenReturn(true); + + assertTrue(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element, never()).configDnsSupportForSubnet(network, nicProfile, vmProfile, dest, context); + verify(element).addDnsEntry(network, nicProfile, vmProfile, dest, context); + } + + @Test + public void prepareElementAddsUserDataWhenUserDataIsSupported() throws Exception { + UserDataServiceProvider element = mock(UserDataServiceProvider.class); + when(element.getProvider()).thenReturn(PROVIDER); + support(Service.UserData); + when(element.addPasswordAndUserdata(network, nicProfile, vmProfile, dest, context)).thenReturn(true); + + assertTrue(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element).addPasswordAndUserdata(network, nicProfile, vmProfile, dest, context); + } + + @Test + public void prepareElementCreatesConfigDriveIsoWhenAnySideEffectServiceIsSupported() throws Exception { + ConfigDriveNetworkElement element = mock(ConfigDriveNetworkElement.class); + when(element.getProvider()).thenReturn(PROVIDER); + support(Service.UserData); + when(element.addPasswordAndUserdata(network, nicProfile, vmProfile, dest, context)).thenReturn(true); + when(element.createConfigDriveIso(nicProfile, vmProfile, dest, null)).thenReturn(true); + + assertTrue(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element).addPasswordAndUserdata(network, nicProfile, vmProfile, dest, context); + verify(element).createConfigDriveIso(nicProfile, vmProfile, dest, null); + } + + @Test + public void prepareElementSkipsSideEffectsForNonUserVm() throws Exception { + DhcpServiceProvider element = mock(DhcpServiceProvider.class); + when(element.getProvider()).thenReturn(PROVIDER); + when(vmProfile.getType()).thenReturn(VirtualMachine.Type.DomainRouter); + + assertTrue(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element).prepare(network, nicProfile, vmProfile, dest, context); + verify(networkModel, never()).areServicesSupportedInNetwork(NETWORK_ID, Service.Dhcp); + verify(element, never()).addDhcpEntry(network, nicProfile, vmProfile, dest, context); + } + + @Test + public void prepareElementSkipsSideEffectsForNullProvider() throws Exception { + NetworkElement element = mock(NetworkElement.class); + when(element.getProvider()).thenReturn(null); + + assertTrue(service.prepareElement(element, network, nicProfile, vmProfile, dest, context)); + + verify(element).prepare(network, nicProfile, vmProfile, dest, context); + verify(networkModel, never()).areServicesSupportedInNetwork(NETWORK_ID, Service.Dhcp); + } + + private void support(Service serviceName) { + when(networkModel.areServicesSupportedInNetwork(NETWORK_ID, serviceName)).thenReturn(true); + when(networkModel.isProviderSupportServiceInNetwork(NETWORK_ID, serviceName, PROVIDER)).thenReturn(true); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicImportServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicImportServiceImplTest.java new file mode 100644 index 000000000000..002c3cceecf7 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicImportServiceImplTest.java @@ -0,0 +1,325 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import com.cloud.dc.DataCenter; +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.InsufficientVirtualNetworkCapacityException; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.IpAddress.State; +import com.cloud.network.IpAddressManager; +import com.cloud.network.Network; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Service; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Ip; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.NicDao; +import com.cloud.user.dao.AccountDao; + +@RunWith(JUnit4.class) +public class NicImportServiceImplTest { + + NicImportServiceImpl importService; + + private static final long networkOfferingId = 1L; + + @Before + public void setUp() { + importService = Mockito.spy(new NicImportServiceImpl()); + importService.nicDao = mock(NicDao.class); + importService.networksDao = mock(NetworkDao.class); + importService.ipAddressDao = mock(IPAddressDao.class); + importService.vlanDao = mock(VlanDao.class); + importService.networkModel = mock(NetworkModel.class); + importService.ipAddrMgr = mock(IpAddressManager.class); + importService.accountDao = mock(AccountDao.class); + importService.nicProfileMtuService = mock(NicProfileMtuService.class); + } + + @Test + public void testGetNetworkGatewayAndNetmaskForNicImportAdvancedZone() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + String ipAddress = "10.1.1.10"; + + String networkGateway = "10.1.1.1"; + String networkNetmask = "255.255.255.0"; + String networkCidr = "10.1.1.0/24"; + Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); + Mockito.when(network.getGateway()).thenReturn(networkGateway); + Mockito.when(network.getCidr()).thenReturn(networkCidr); + Pair pair = importService.getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, ipAddress); + Assert.assertNotNull(pair); + Assert.assertEquals(networkGateway, pair.first()); + Assert.assertEquals(networkNetmask, pair.second()); + } + + @Test + public void testGetNetworkGatewayAndNetmaskForNicImportBasicZone() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); + String ipAddress = "172.1.1.10"; + + String defaultNetworkGateway = "172.1.1.1"; + String defaultNetworkNetmask = "255.255.255.0"; + VlanVO vlan = Mockito.mock(VlanVO.class); + Mockito.when(vlan.getVlanGateway()).thenReturn(defaultNetworkGateway); + Mockito.when(vlan.getVlanNetmask()).thenReturn(defaultNetworkNetmask); + Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); + Mockito.when(ipAddressVO.getVlanId()).thenReturn(1L); + Mockito.when(importService.vlanDao.findById(1L)).thenReturn(vlan); + Mockito.when(importService.ipAddressDao.findByIp(ipAddress)).thenReturn(ipAddressVO); + Pair pair = importService.getNetworkGatewayAndNetmaskForNicImport(network, dataCenter, ipAddress); + Assert.assertNotNull(pair); + Assert.assertEquals(defaultNetworkGateway, pair.first()); + Assert.assertEquals(defaultNetworkNetmask, pair.second()); + } + + @Test + public void testGetGuestIpForNicImportL2Network() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.L2); + Assert.assertNull(importService.getSelectedIpForNicImport(network, dataCenter, ipAddresses)); + } + + @Test + public void testGetGuestIpForNicImportAdvancedZone() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated); + Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Advanced); + String ipAddress = "10.1.10.10"; + Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); + Mockito.when(importService.ipAddrMgr.acquireGuestIpAddress(network, ipAddress)).thenReturn(ipAddress); + String guestIp = importService.getSelectedIpForNicImport(network, dataCenter, ipAddresses); + Assert.assertEquals(ipAddress, guestIp); + } + + @Test + public void testGetGuestIpForNicImportBasicZoneAutomaticIP() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared); + Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); + long networkId = 1L; + long dataCenterId = 1L; + String freeIp = "172.10.10.10"; + IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); + Ip ip = mock(Ip.class); + Mockito.when(ip.addr()).thenReturn(freeIp); + Mockito.when(ipAddressVO.getAddress()).thenReturn(ip); + Mockito.when(ipAddressVO.getState()).thenReturn(State.Free); + Mockito.when(network.getId()).thenReturn(networkId); + Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); + Mockito.when(importService.ipAddressDao.findBySourceNetworkIdAndDatacenterIdAndState(networkId, dataCenterId, State.Free)).thenReturn(ipAddressVO); + String ipAddress = importService.getSelectedIpForNicImport(network, dataCenter, ipAddresses); + Assert.assertEquals(freeIp, ipAddress); + } + + @Test + public void testGetGuestIpForNicImportBasicZoneManualIP() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared); + Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); + long networkId = 1L; + long dataCenterId = 1L; + String requestedIp = "172.10.10.10"; + IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); + Ip ip = mock(Ip.class); + Mockito.when(ip.addr()).thenReturn(requestedIp); + Mockito.when(ipAddressVO.getAddress()).thenReturn(ip); + Mockito.when(ipAddressVO.getState()).thenReturn(State.Free); + Mockito.when(network.getId()).thenReturn(networkId); + Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); + Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp); + Mockito.when(importService.ipAddressDao.findByIpAndSourceNetworkId(networkId, requestedIp)).thenReturn(ipAddressVO); + String ipAddress = importService.getSelectedIpForNicImport(network, dataCenter, ipAddresses); + Assert.assertEquals(requestedIp, ipAddress); + } + + @Test(expected = CloudRuntimeException.class) + public void testGetGuestIpForNicImportBasicUsedIP() { + Network network = Mockito.mock(Network.class); + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.Shared); + Mockito.when(dataCenter.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); + long networkId = 1L; + long dataCenterId = 1L; + String requestedIp = "172.10.10.10"; + IPAddressVO ipAddressVO = Mockito.mock(IPAddressVO.class); + Ip ip = mock(Ip.class); + Mockito.when(ip.addr()).thenReturn(requestedIp); + Mockito.when(ipAddressVO.getAddress()).thenReturn(ip); + Mockito.when(ipAddressVO.getState()).thenReturn(State.Allocated); + Mockito.when(network.getId()).thenReturn(networkId); + Mockito.when(dataCenter.getId()).thenReturn(dataCenterId); + Mockito.when(ipAddresses.getIp4Address()).thenReturn(requestedIp); + Mockito.when(importService.ipAddressDao.findByIp(requestedIp)).thenReturn(ipAddressVO); + importService.getSelectedIpForNicImport(network, dataCenter, ipAddresses); + } + + @Test(expected = InsufficientVirtualNetworkCapacityException.class) + public void testImportNicAcquireGuestIPFailed() throws Exception { + DataCenter dataCenter = Mockito.mock(DataCenter.class); + VirtualMachine vm = mock(VirtualMachine.class); + Network network = Mockito.mock(Network.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated); + Mockito.when(network.getNetworkOfferingId()).thenReturn(networkOfferingId); + long dataCenterId = 1L; + Mockito.when(network.getDataCenterId()).thenReturn(dataCenterId); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + String ipAddress = "10.1.10.10"; + Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); + Mockito.doReturn(null).when(importService).getSelectedIpForNicImport(network, dataCenter, ipAddresses); + Mockito.when(importService.networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); + String macAddress = "02:01:01:82:00:01"; + int deviceId = 0; + importService.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + } + + @Test(expected = InsufficientVirtualNetworkCapacityException.class) + public void testImportNicAutoAcquireGuestIPFailed() throws Exception { + DataCenter dataCenter = Mockito.mock(DataCenter.class); + VirtualMachine vm = mock(VirtualMachine.class); + Network network = Mockito.mock(Network.class); + Mockito.when(network.getGuestType()).thenReturn(GuestType.Isolated); + Mockito.when(network.getNetworkOfferingId()).thenReturn(networkOfferingId); + long dataCenterId = 1L; + Mockito.when(network.getDataCenterId()).thenReturn(dataCenterId); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + String ipAddress = "auto"; + Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); + Mockito.doReturn(null).when(importService).getSelectedIpForNicImport(network, dataCenter, ipAddresses); + Mockito.when(importService.networkModel.listNetworkOfferingServices(networkOfferingId)).thenReturn(Arrays.asList(Service.Dns, Service.Dhcp)); + String macAddress = "02:01:01:82:00:01"; + int deviceId = 0; + importService.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + } + + @Test + public void testImportNicNoIP4Address() throws Exception { + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Long vmId = 1L; + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + VirtualMachine vm = mock(VirtualMachine.class); + Mockito.when(vm.getId()).thenReturn(vmId); + Mockito.when(vm.getHypervisorType()).thenReturn(hypervisorType); + Long networkId = 1L; + Network network = Mockito.mock(Network.class); + Mockito.when(network.getId()).thenReturn(networkId); + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(ipAddresses.getIp4Address()).thenReturn(null); + URI broadcastUri = URI.create("vlan://123"); + NicVO nic = mock(NicVO.class); + Mockito.when(nic.getBroadcastUri()).thenReturn(broadcastUri); + String macAddress = "02:01:01:82:00:01"; + int deviceId = 1; + Integer networkRate = 200; + Mockito.when(importService.networkModel.getNetworkRate(networkId, vmId)).thenReturn(networkRate); + Mockito.when(importService.networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(false); + Mockito.when(importService.networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag"); + try (MockedStatic transactionMocked = Mockito.mockStatic(Transaction.class)) { + transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic); + Pair nicProfileIntegerPair = importService.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + verify(importService.networkModel, times(1)).getNetworkRate(networkId, vmId); + verify(importService.networkModel, times(1)).isSecurityGroupSupportedInNetwork(network); + verify(importService.networkModel, times(1)).getNetworkTag(Hypervisor.HypervisorType.KVM, network); + assertEquals(deviceId, nicProfileIntegerPair.second().intValue()); + NicProfile nicProfile = nicProfileIntegerPair.first(); + assertEquals(broadcastUri, nicProfile.getBroadCastUri()); + assertEquals(networkRate, nicProfile.getNetworkRate()); + assertFalse(nicProfile.isSecurityGroupEnabled()); + assertEquals("testtag", nicProfile.getName()); + } + } + + @Test + public void testImportNicWithIP4Address() throws Exception { + DataCenter dataCenter = Mockito.mock(DataCenter.class); + Long vmId = 1L; + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + VirtualMachine vm = mock(VirtualMachine.class); + Mockito.when(vm.getId()).thenReturn(vmId); + Mockito.when(vm.getHypervisorType()).thenReturn(hypervisorType); + Long networkId = 1L; + Network network = Mockito.mock(Network.class); + Mockito.when(network.getId()).thenReturn(networkId); + String ipAddress = "10.1.10.10"; + Network.IpAddresses ipAddresses = Mockito.mock(Network.IpAddresses.class); + Mockito.when(ipAddresses.getIp4Address()).thenReturn(ipAddress); + URI broadcastUri = URI.create("vlan://123"); + NicVO nic = mock(NicVO.class); + Mockito.when(nic.getBroadcastUri()).thenReturn(broadcastUri); + String macAddress = "02:01:01:82:00:01"; + int deviceId = 1; + Integer networkRate = 200; + Mockito.when(importService.networkModel.getNetworkRate(networkId, vmId)).thenReturn(networkRate); + Mockito.when(importService.networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(false); + Mockito.when(importService.networkModel.getNetworkTag(hypervisorType, network)).thenReturn("testtag"); + try (MockedStatic transactionMocked = Mockito.mockStatic(Transaction.class)) { + transactionMocked.when(() -> Transaction.execute(any(TransactionCallback.class))).thenReturn(nic); + Pair nicProfileIntegerPair = importService.importNic(macAddress, deviceId, network, true, vm, ipAddresses, dataCenter, false); + verify(importService, times(1)).getSelectedIpForNicImport(network, dataCenter, ipAddresses); + verify(importService.networkModel, times(1)).getNetworkRate(networkId, vmId); + verify(importService.networkModel, times(1)).isSecurityGroupSupportedInNetwork(network); + verify(importService.networkModel, times(1)).getNetworkTag(Hypervisor.HypervisorType.KVM, network); + assertEquals(deviceId, nicProfileIntegerPair.second().intValue()); + NicProfile nicProfile = nicProfileIntegerPair.first(); + assertEquals(broadcastUri, nicProfile.getBroadCastUri()); + assertEquals(networkRate, nicProfile.getNetworkRate()); + assertFalse(nicProfile.isSecurityGroupEnabled()); + assertEquals("testtag", nicProfile.getName()); + } + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicMigrationServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicMigrationServiceImplTest.java new file mode 100644 index 000000000000..5fa2ef32ffce --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicMigrationServiceImplTest.java @@ -0,0 +1,469 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; + +import com.cloud.dc.Vlan; +import com.cloud.dc.VlanVO; +import com.cloud.deploy.DeployDestination; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network.GuestType; +import com.cloud.network.Network.Provider; +import com.cloud.network.NetworkMigrationResponder; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.AddressFormat; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.IsolationType; +import com.cloud.network.Networks.Mode; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkServiceMapDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.PhysicalNetworkDao; +import com.cloud.network.dao.PhysicalNetworkVO; +import com.cloud.network.element.NetworkElement; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Ip; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.ReservationContext; +import com.cloud.vm.UserVmManager; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.NicDao; +import com.cloud.dc.dao.VlanDao; + +public class NicMigrationServiceImplTest { + + private static final String GURU_NAME = "testGuru"; + private static final long VM_ID = 42L; + private static final long HOST_ID = 101L; + private static final long DST_HOST_ID = 202L; + private static final long NETWORK_ID = 11L; + private static final long PUBLIC_NETWORK_ID = 22L; + private static final long PHYSICAL_NETWORK_ID = 33L; + private static final long VLAN_ID = 44L; + private static final String VLAN_TAG = "123"; + + NicMigrationServiceImpl service; + DeployDestination dest; + + @Before + public void setUp() { + service = Mockito.spy(new NicMigrationServiceImpl()); + service.nicDao = mock(NicDao.class); + service.networksDao = mock(NetworkDao.class); + service.networkModel = mock(NetworkModel.class); + service.networkServiceMapDao = mock(NetworkServiceMapDao.class); + service.ipAddressDao = mock(IPAddressDao.class); + service.vlanDao = mock(VlanDao.class); + service.physicalNetworkDao = mock(PhysicalNetworkDao.class); + service.userVmManager = mock(UserVmManager.class); + service.networkGurus = new ArrayList<>(); + service.networkElements = new ArrayList<>(); + dest = mock(DeployDestination.class); + } + + @Test + public void testPrepareNicForMigrationCallsGuruAndElementResponders() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO network = newNetwork(NETWORK_ID, GuestType.Shared, TrafficType.Guest); + NicVO nic = newNic(NETWORK_ID); + NetworkGuru guru = addGuru(true); + NetworkMigrationResponder guruResponder = (NetworkMigrationResponder) guru; + NetworkElement element = addElement(Provider.VirtualRouter, true); + NetworkMigrationResponder elementResponder = (NetworkMigrationResponder) element; + stubPrepareNetwork(vm, network, nic, Collections.singletonList(Provider.VirtualRouter)); + when(guruResponder.prepareMigration(any(NicProfile.class), eq(network), eq(vm), eq(dest), any(ReservationContext.class))).thenReturn(true); + when(elementResponder.prepareMigration(any(NicProfile.class), eq(network), eq(vm), eq(dest), any(ReservationContext.class))).thenReturn(true); + + service.prepareNicForMigration(vm, dest); + + ArgumentCaptor profileCaptor = ArgumentCaptor.forClass(NicProfile.class); + verify(guruResponder).prepareMigration(profileCaptor.capture(), eq(network), eq(vm), eq(dest), any(ReservationContext.class)); + NicProfile profile = profileCaptor.getValue(); + verify(elementResponder).prepareMigration(eq(profile), eq(network), eq(vm), eq(dest), any(ReservationContext.class)); + verify(guru).updateNicProfile(profile, network); + verify(vm).addNic(profile); + } + + @Test + public void testPrepareNicForMigrationSkipsNonResponderGuruAndElement() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO network = newNetwork(NETWORK_ID, GuestType.Shared, TrafficType.Guest); + NicVO nic = newNic(NETWORK_ID); + NetworkGuru guru = addGuru(false); + addElement(Provider.VirtualRouter, false); + stubPrepareNetwork(vm, network, nic, Collections.singletonList(Provider.VirtualRouter)); + + service.prepareNicForMigration(vm, dest); + + verify(guru).updateNicProfile(any(NicProfile.class), eq(network)); + verify(vm).addNic(any(NicProfile.class)); + } + + @Test + public void testPrepareNicForMigrationSetsUpPvlanForUserL2Network() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO network = newNetwork(NETWORK_ID, GuestType.L2, TrafficType.Guest); + NicVO nic = newNic(NETWORK_ID); + addGuru(false); + stubPrepareNetwork(vm, network, nic, Collections.emptyList()); + + service.prepareNicForMigration(vm, dest); + + verify(service.userVmManager).setupVmForPvlan(eq(false), eq(HOST_ID), any(NicProfile.class)); + } + + @Test + public void testPrepareNicForMigrationThrowsWhenProviderDisabled() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO network = newNetwork(NETWORK_ID, GuestType.Shared, TrafficType.Guest); + NicVO nic = newNic(NETWORK_ID); + addGuru(false); + addElement(Provider.VirtualRouter, false); + stubPrepareNetwork(vm, network, nic, Collections.singletonList(Provider.VirtualRouter)); + when(service.networkModel.isProviderEnabledInPhysicalNetwork(PHYSICAL_NETWORK_ID, Provider.VirtualRouter.getName())).thenReturn(false); + + try { + service.prepareNicForMigration(vm, dest); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("physical network id: " + PHYSICAL_NETWORK_ID)); + } + } + + @Test + public void testPrepareNicForMigrationDomainRouterKvmUsesAllNicsPath() { + VirtualMachineProfile vm = newVm(Type.DomainRouter, HypervisorType.KVM, HOST_ID); + NetworkVO guestNetwork = newNetwork(NETWORK_ID, GuestType.Isolated, TrafficType.Guest); + NetworkVO publicNetwork = newNetwork(PUBLIC_NETWORK_ID, GuestType.Shared, TrafficType.Public); + NicVO nic = newNic(NETWORK_ID); + IPAddressVO publicIp = newPublicIp("203.0.113.10"); + VlanVO vlan = newVlan(); + addGuru(false); + stubPrepareNetwork(vm, guestNetwork, nic, Collections.emptyList()); + stubNetworkLookups(vm, publicNetwork); + when(service.ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Collections.singletonList(publicIp)); + when(service.vlanDao.findById(VLAN_ID)).thenReturn(vlan); + when(service.nicDao.findByNetworkIdInstanceIdAndBroadcastUri(eq(PUBLIC_NETWORK_ID), eq(VM_ID), any(String.class))).thenReturn(null); + + service.prepareNicForMigration(vm, dest); + + verify(service.ipAddressDao).listByAssociatedNetwork(NETWORK_ID, null); + verify(vm, times(2)).addNic(any(NicProfile.class)); + } + + @Test + public void testPrepareAllNicsForMigrationCreatesDummyPublicIpProfileWhenDbNicMissing() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO guestNetwork = newNetwork(NETWORK_ID, GuestType.Isolated, TrafficType.Guest); + NetworkVO publicNetwork = newNetwork(PUBLIC_NETWORK_ID, GuestType.Shared, TrafficType.Public); + NicVO nic = newNic(NETWORK_ID); + IPAddressVO userIp = newPublicIp("203.0.113.10"); + VlanVO vlan = newVlan(); + addGuru(false); + stubPrepareNetwork(vm, guestNetwork, nic, Collections.emptyList()); + stubNetworkLookups(vm, publicNetwork); + when(service.ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Collections.singletonList(userIp)); + when(service.vlanDao.findById(VLAN_ID)).thenReturn(vlan); + when(service.nicDao.findByNetworkIdInstanceIdAndBroadcastUri(eq(PUBLIC_NETWORK_ID), eq(VM_ID), any(String.class))).thenReturn(null); + + service.prepareAllNicsForMigration(vm, dest); + + ArgumentCaptor profileCaptor = ArgumentCaptor.forClass(NicProfile.class); + verify(vm, times(2)).addNic(profileCaptor.capture()); + NicProfile dummyProfile = findDummyProfile(profileCaptor.getAllValues()); + assertNotNull(dummyProfile); + assertEquals(Integer.valueOf(255), dummyProfile.getDeviceId()); + assertEquals(userIp.getAddress().toString(), dummyProfile.getIPv4Address()); + assertEquals(vlan.getVlanGateway(), dummyProfile.getIPv4Gateway()); + assertEquals(vlan.getVlanNetmask(), dummyProfile.getIPv4Netmask()); + assertTrue(dummyProfile.getMacAddress().startsWith("1e:01:")); + assertTrue(dummyProfile.getMacAddress().endsWith(":00:04:d2")); + assertEquals(BroadcastDomainType.Vlan.toUri(VLAN_TAG), dummyProfile.getBroadCastUri()); + assertEquals(IsolationType.Vlan.toUri(VLAN_TAG), dummyProfile.getIsolationUri()); + assertEquals(PUBLIC_NETWORK_ID, dummyProfile.getNetworkId()); + assertEquals(Integer.valueOf(200), dummyProfile.getNetworkRate()); + assertEquals("tag-" + PUBLIC_NETWORK_ID, dummyProfile.getName()); + } + + @Test + public void testPrepareAllNicsForMigrationDoesNotCreateDummyProfileWhenDbNicExists() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO guestNetwork = newNetwork(NETWORK_ID, GuestType.Isolated, TrafficType.Guest); + NicVO nic = newNic(NETWORK_ID); + IPAddressVO userIp = newPublicIp("203.0.113.10"); + VlanVO vlan = newVlan(); + addGuru(false); + stubPrepareNetwork(vm, guestNetwork, nic, Collections.emptyList()); + when(service.ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Collections.singletonList(userIp)); + when(service.vlanDao.findById(VLAN_ID)).thenReturn(vlan); + when(service.nicDao.findByNetworkIdInstanceIdAndBroadcastUri(eq(PUBLIC_NETWORK_ID), eq(VM_ID), any(String.class))).thenReturn(mock(NicVO.class)); + + service.prepareAllNicsForMigration(vm, dest); + + verify(vm, times(1)).addNic(any(NicProfile.class)); + } + + @Test + public void testPrepareAllNicsForMigrationDeduplicatesPublicIpBroadcastUris() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO guestNetwork = newNetwork(NETWORK_ID, GuestType.Isolated, TrafficType.Guest); + NetworkVO publicNetwork = newNetwork(PUBLIC_NETWORK_ID, GuestType.Shared, TrafficType.Public); + NicVO nic = newNic(NETWORK_ID); + IPAddressVO firstIp = newPublicIp("203.0.113.10"); + IPAddressVO secondIp = newPublicIp("203.0.113.11"); + VlanVO vlan = newVlan(); + addGuru(false); + stubPrepareNetwork(vm, guestNetwork, nic, Collections.emptyList()); + stubNetworkLookups(vm, publicNetwork); + when(service.ipAddressDao.listByAssociatedNetwork(NETWORK_ID, null)).thenReturn(Arrays.asList(firstIp, secondIp)); + when(service.vlanDao.findById(VLAN_ID)).thenReturn(vlan); + when(service.nicDao.findByNetworkIdInstanceIdAndBroadcastUri(eq(PUBLIC_NETWORK_ID), eq(VM_ID), any(String.class))).thenReturn(null); + + service.prepareAllNicsForMigration(vm, dest); + + verify(vm, times(2)).addNic(any(NicProfile.class)); + } + + @Test + public void testPrepareAllNicsForMigrationThrowsWithPhysicalNetworkInMessageWhenProviderDisabled() { + VirtualMachineProfile vm = newVm(Type.User, HypervisorType.KVM, HOST_ID); + NetworkVO network = newNetwork(NETWORK_ID, GuestType.Shared, TrafficType.Guest); + NicVO nic = newNic(NETWORK_ID); + addGuru(false); + addElement(Provider.VirtualRouter, false); + stubPrepareNetwork(vm, network, nic, Collections.singletonList(Provider.VirtualRouter)); + when(service.networkModel.isProviderEnabledInPhysicalNetwork(PHYSICAL_NETWORK_ID, Provider.VirtualRouter.getName())).thenReturn(false); + when(service.physicalNetworkDao.findById(PHYSICAL_NETWORK_ID)).thenReturn(mock(PhysicalNetworkVO.class)); + + try { + service.prepareAllNicsForMigration(vm, dest); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("physical network:")); + verify(service.physicalNetworkDao).findById(PHYSICAL_NETWORK_ID); + } + } + + @Test + public void testCommitNicForMigrationCallsRespondersPvlanAndPersistsReservation() { + NetworkVO network = newNetwork(NETWORK_ID, GuestType.L2, TrafficType.Guest); + VirtualMachineProfile src = newVmWithNics(Type.User, HypervisorType.KVM, HOST_ID, nicProfile(9L, NETWORK_ID, "src-reservation")); + VirtualMachineProfile dst = newVmWithNics(Type.User, HypervisorType.KVM, DST_HOST_ID, nicProfile(9L, NETWORK_ID, "dst-reservation")); + NetworkGuru guru = addGuru(true); + NetworkMigrationResponder guruResponder = (NetworkMigrationResponder) guru; + NetworkElement element = addElement(Provider.VirtualRouter, true); + NetworkMigrationResponder elementResponder = (NetworkMigrationResponder) element; + NicVO persistedNic = mock(NicVO.class); + stubNetworkLookups(src, network, Collections.singletonList(Provider.VirtualRouter)); + when(service.nicDao.findById(9L)).thenReturn(persistedNic); + + service.commitNicForMigration(src, dst); + + NicProfile nicSrc = src.getNics().get(0); + verify(guruResponder).commitMigration(eq(nicSrc), eq(network), eq(src), any(ReservationContext.class), any(ReservationContext.class)); + verify(elementResponder).commitMigration(eq(nicSrc), eq(network), eq(src), any(ReservationContext.class), any(ReservationContext.class)); + verify(service.userVmManager).setupVmForPvlan(eq(true), eq(HOST_ID), eq(nicSrc)); + verify(service.nicDao).findById(9L); + verify(persistedNic).setReservationId("dst-reservation"); + verify(service.nicDao).persist(persistedNic); + } + + @Test + public void testCommitNicForMigrationSkipsNonResponderGuruAndElementButPersistsReservation() { + NetworkVO network = newNetwork(NETWORK_ID, GuestType.Shared, TrafficType.Guest); + VirtualMachineProfile src = newVmWithNics(Type.User, HypervisorType.KVM, HOST_ID, nicProfile(9L, NETWORK_ID, "src-reservation")); + VirtualMachineProfile dst = newVmWithNics(Type.User, HypervisorType.KVM, DST_HOST_ID, nicProfile(9L, NETWORK_ID, "dst-reservation")); + NetworkGuru guru = addGuru(false); + NetworkElement element = addElement(Provider.VirtualRouter, false); + NicVO persistedNic = mock(NicVO.class); + stubNetworkLookups(src, network, Collections.singletonList(Provider.VirtualRouter)); + when(service.nicDao.findById(9L)).thenReturn(persistedNic); + + service.commitNicForMigration(src, dst); + + verify(guru, never()).release(any(NicProfile.class), any(VirtualMachineProfile.class), any(String.class)); + verify(element, times(2)).getProvider(); + verify(persistedNic).setReservationId("dst-reservation"); + verify(service.nicDao).persist(persistedNic); + } + + @Test + public void testRollbackNicForMigrationCallsRespondersAndPvlan() { + NetworkVO network = newNetwork(NETWORK_ID, GuestType.L2, TrafficType.Guest); + VirtualMachineProfile src = newVmWithNics(Type.User, HypervisorType.KVM, HOST_ID, nicProfile(9L, NETWORK_ID, "src-reservation")); + VirtualMachineProfile dst = newVmWithNics(Type.User, HypervisorType.KVM, DST_HOST_ID, nicProfile(9L, NETWORK_ID, "dst-reservation")); + NetworkGuru guru = addGuru(true); + NetworkMigrationResponder guruResponder = (NetworkMigrationResponder) guru; + NetworkElement element = addElement(Provider.VirtualRouter, true); + NetworkMigrationResponder elementResponder = (NetworkMigrationResponder) element; + stubNetworkLookups(dst, network, Collections.singletonList(Provider.VirtualRouter)); + + service.rollbackNicForMigration(src, dst); + + NicProfile nicDst = dst.getNics().get(0); + verify(guruResponder).rollbackMigration(eq(nicDst), eq(network), eq(dst), any(ReservationContext.class), any(ReservationContext.class)); + verify(elementResponder).rollbackMigration(eq(nicDst), eq(network), eq(dst), any(ReservationContext.class), any(ReservationContext.class)); + verify(service.userVmManager).setupVmForPvlan(eq(true), eq(DST_HOST_ID), eq(nicDst)); + } + + private VirtualMachineProfile newVm(Type type, HypervisorType hypervisorType, Long hostId) { + VirtualMachineProfile vm = mock(VirtualMachineProfile.class); + VirtualMachine virtualMachine = mock(VirtualMachine.class); + when(vm.getId()).thenReturn(VM_ID); + when(vm.getType()).thenReturn(type); + when(vm.getHypervisorType()).thenReturn(hypervisorType); + when(vm.getVirtualMachine()).thenReturn(virtualMachine); + when(virtualMachine.getHostId()).thenReturn(hostId); + return vm; + } + + private VirtualMachineProfile newVmWithNics(Type type, HypervisorType hypervisorType, Long hostId, NicProfile... nics) { + VirtualMachineProfile vm = newVm(type, hypervisorType, hostId); + when(vm.getNics()).thenReturn(Arrays.asList(nics)); + return vm; + } + + private NetworkVO newNetwork(long id, GuestType guestType, TrafficType trafficType) { + NetworkVO network = mock(NetworkVO.class); + when(network.getId()).thenReturn(id); + when(network.getGuruName()).thenReturn(GURU_NAME); + when(network.getGuestType()).thenReturn(guestType); + when(network.getTrafficType()).thenReturn(trafficType); + when(network.getPhysicalNetworkId()).thenReturn(PHYSICAL_NETWORK_ID); + when(network.getMode()).thenReturn(Mode.Dhcp); + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Vlan); + return network; + } + + private NicVO newNic(long networkId) { + NicVO nic = new NicVO(GURU_NAME, VM_ID, networkId, Type.User); + nic.setAddressFormat(AddressFormat.Ip4); + nic.setBroadcastUri(URI.create("vlan://" + networkId)); + nic.setIsolationUri(IsolationType.Vlan.toUri(String.valueOf(networkId))); + nic.setIPv4Address("10.0.0.10"); + nic.setIPv4Gateway("10.0.0.1"); + nic.setIPv4Netmask("255.255.255.0"); + nic.setMacAddress("02:00:00:00:00:01"); + nic.setDeviceId(1); + nic.setReservationId("reservation-" + networkId); + nic.setReservationStrategy(Nic.ReservationStrategy.Start); + return nic; + } + + private NicProfile nicProfile(long id, long networkId, String reservationId) { + NicProfile profile = new NicProfile(); + profile.setId(id); + profile.setNetworkId(networkId); + profile.setReservationId(reservationId); + return profile; + } + + private NetworkGuru addGuru(boolean responder) { + NetworkGuru guru = responder ? mock(NetworkGuru.class, Mockito.withSettings().extraInterfaces(NetworkMigrationResponder.class)) : mock(NetworkGuru.class); + when(guru.getName()).thenReturn(GURU_NAME); + service.networkGurus.add(guru); + return guru; + } + + private NetworkElement addElement(Provider provider, boolean responder) { + NetworkElement element = responder ? mock(NetworkElement.class, Mockito.withSettings().extraInterfaces(NetworkMigrationResponder.class)) : mock(NetworkElement.class); + when(element.getProvider()).thenReturn(provider); + when(element.getName()).thenReturn(provider.getName()); + service.networkElements.add(element); + return element; + } + + private void stubPrepareNetwork(VirtualMachineProfile vm, NetworkVO network, NicVO nic, List providers) { + when(service.nicDao.listByVmId(VM_ID)).thenReturn(Collections.singletonList(nic)); + stubNetworkLookups(vm, network, providers); + } + + private void stubNetworkLookups(VirtualMachineProfile vm, NetworkVO network) { + stubNetworkLookups(vm, network, Collections.emptyList()); + } + + private void stubNetworkLookups(VirtualMachineProfile vm, NetworkVO network, List providers) { + long networkId = network.getId(); + HypervisorType hypervisorType = vm.getHypervisorType(); + when(service.networksDao.findById(networkId)).thenReturn(network); + when(service.networkModel.getNetworkRate(networkId, VM_ID)).thenReturn(networkId == PUBLIC_NETWORK_ID ? 200 : 100); + when(service.networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(false); + when(service.networkModel.getNetworkTag(hypervisorType, network)).thenReturn("tag-" + networkId); + when(service.networkModel.getPhysicalNetworkId(network)).thenReturn(PHYSICAL_NETWORK_ID); + when(service.networkModel.isProviderEnabledInPhysicalNetwork(anyLong(), any(String.class))).thenReturn(true); + when(service.networkServiceMapDao.getDistinctProviders(networkId)).thenReturn(providerNames(providers)); + } + + private List providerNames(List providers) { + List names = new ArrayList<>(); + for (Provider provider : providers) { + names.add(provider.getName()); + } + return names; + } + + private IPAddressVO newPublicIp(String address) { + IPAddressVO ip = new IPAddressVO(new Ip(address), 1L, 1234L, VLAN_ID, false); + ip.setAssociatedWithNetworkId(NETWORK_ID); + return ip; + } + + private VlanVO newVlan() { + return new VlanVO(Vlan.VlanType.VirtualNetwork, VLAN_TAG, "203.0.113.1", "255.255.255.0", 1L, "203.0.113.2-203.0.113.254", PUBLIC_NETWORK_ID, + PHYSICAL_NETWORK_ID, null, null, null); + } + + private NicProfile findDummyProfile(List profiles) { + for (NicProfile profile : profiles) { + if (Integer.valueOf(255).equals(profile.getDeviceId())) { + return profile; + } + } + return null; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingServiceImplTest.java new file mode 100644 index 000000000000..274d723cf572 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicProfileLifecycleMappingServiceImplTest.java @@ -0,0 +1,471 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.acl.ControlledEntity.ACLType; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.NetworkProfile; +import com.cloud.network.Networks.AddressFormat; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.Mode; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.guru.NetworkGuru; +import com.cloud.vm.Nic; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.NicDao; + +public class NicProfileLifecycleMappingServiceImplTest { + + private static final long VM_ID = 101L; + private static final long NETWORK_ID = 202L; + private static final String GURU_NAME = "GuestNetworkGuru"; + + private NicProfileLifecycleMappingServiceImpl service; + private NicDao nicDao; + private NetworkDao networksDao; + private NetworkModel networkModel; + private NetworkGuru guru; + + @Before + public void setUp() { + service = new NicProfileLifecycleMappingServiceImpl(); + nicDao = mock(NicDao.class); + networksDao = mock(NetworkDao.class); + networkModel = mock(NetworkModel.class); + guru = mock(NetworkGuru.class); + when(guru.getName()).thenReturn(GURU_NAME); + service.nicDao = nicDao; + service.networksDao = networksDao; + service.networkModel = networkModel; + service.setNetworkGurus(Collections.singletonList(guru)); + } + + @Test + public void applyProfileToNicUsesProfileDeviceIdAndCopiesAllocationFields() { + NicVO nic = new NicVO("reserver", VM_ID, NETWORK_ID, VirtualMachine.Type.User); + NicProfile profile = profileWithAllocatedFields(); + profile.setDeviceId(7); + + Integer nextDeviceId = service.applyProfileToNic(nic, profile, 3); + + assertEquals(Integer.valueOf(3), nextDeviceId); + assertEquals(7, nic.getDeviceId()); + assertEquals(Nic.ReservationStrategy.Create, nic.getReservationStrategy()); + assertTrue(nic.isDefaultNic()); + assertEquals("10.1.1.10", nic.getIPv4Address()); + assertEquals(AddressFormat.DualStack, nic.getAddressFormat()); + assertEquals("02:00:00:00:00:10", nic.getMacAddress()); + assertEquals(Mode.Static, nic.getMode()); + assertEquals("255.255.255.0", nic.getIPv4Netmask()); + assertEquals("10.1.1.1", nic.getIPv4Gateway()); + assertEquals(uri("vlan://101"), nic.getBroadcastUri()); + assertEquals(uri("vlan://201"), nic.getIsolationUri()); + assertEquals(Nic.State.Allocated, nic.getState()); + assertEquals("2001:db8::10", nic.getIPv6Address()); + assertEquals("2001:db8::1", nic.getIPv6Gateway()); + assertEquals("2001:db8::/64", nic.getIPv6Cidr()); + } + + @Test + public void applyProfileToNicUsesCallerDeviceIdAndReturnsIncrementedValue() { + NicVO nic = new NicVO("reserver", VM_ID, NETWORK_ID, VirtualMachine.Type.User); + NicProfile profile = profileWithAllocatedFields(); + + Integer nextDeviceId = service.applyProfileToNic(nic, profile, 3); + + assertEquals(3, nic.getDeviceId()); + assertEquals(Integer.valueOf(4), nextDeviceId); + } + + @Test + public void applyProfileToNicLeavesMacAndUrisUnchangedWhenProfileValuesAreNull() { + NicVO nic = new NicVO("reserver", VM_ID, NETWORK_ID, VirtualMachine.Type.User); + URI originalBroadcastUri = uri("vlan://301"); + URI originalIsolationUri = uri("vlan://401"); + nic.setMacAddress("02:00:00:00:00:aa"); + nic.setBroadcastUri(originalBroadcastUri); + nic.setIsolationUri(originalIsolationUri); + NicProfile profile = new NicProfile(); + + service.applyProfileToNic(nic, profile, 1); + + assertEquals("02:00:00:00:00:aa", nic.getMacAddress()); + assertEquals(originalBroadcastUri, nic.getBroadcastUri()); + assertEquals(originalIsolationUri, nic.getIsolationUri()); + } + + @Test + public void applyProfileToNicForReleaseCopiesReleaseFieldsAndReservationWhenPresent() { + NicVO nic = new NicVO("reserver", VM_ID, NETWORK_ID, VirtualMachine.Type.User); + NicProfile profile = profileWithAllocatedFields(); + + service.applyProfileToNicForRelease(nic, profile); + + assertEquals("10.1.1.1", nic.getIPv4Gateway()); + assertEquals(AddressFormat.DualStack, nic.getAddressFormat()); + assertEquals("10.1.1.10", nic.getIPv4Address()); + assertEquals("2001:db8::10", nic.getIPv6Address()); + assertEquals("02:00:00:00:00:10", nic.getMacAddress()); + assertEquals(Nic.ReservationStrategy.Create, nic.getReservationStrategy()); + assertEquals(uri("vlan://101"), nic.getBroadcastUri()); + assertEquals(uri("vlan://201"), nic.getIsolationUri()); + assertEquals("255.255.255.0", nic.getIPv4Netmask()); + } + + @Test + public void applyProfileToNicForReleaseDoesNotOverwriteReservationStrategyWithNull() { + NicVO nic = new NicVO("reserver", VM_ID, NETWORK_ID, VirtualMachine.Type.User); + nic.setReservationStrategy(Nic.ReservationStrategy.Start); + + service.applyProfileToNicForRelease(nic, new NicProfile()); + + assertEquals(Nic.ReservationStrategy.Start, nic.getReservationStrategy()); + } + + @Test + public void applyProfileToNetworkCopiesBroadcastDnsAndPhysicalNetwork() { + NetworkVO network = new NetworkVO(); + NetworkProfile profile = mock(NetworkProfile.class); + when(profile.getBroadcastUri()).thenReturn(uri("vlan://501")); + when(profile.getDns1()).thenReturn("1.1.1.1"); + when(profile.getDns2()).thenReturn("9.9.9.9"); + when(profile.getPhysicalNetworkId()).thenReturn(88L); + + service.applyProfileToNetwork(network, profile); + + assertEquals(uri("vlan://501"), network.getBroadcastUri()); + assertEquals("1.1.1.1", network.getDns1()); + assertEquals("9.9.9.9", network.getDns2()); + assertEquals(Long.valueOf(88L), network.getPhysicalNetworkId()); + } + + @Test + public void toNicTOCopiesNicAndNetworkMetadata() { + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.User); + NetworkVO network = networkWithFields(NETWORK_ID, TrafficType.Guest); + NicProfile profile = new NicProfile(); + profile.setIPv4Dns1("8.8.8.8"); + profile.setIPv4Dns2("8.8.4.4"); + when(networkModel.getNetworkRate(NETWORK_ID, null)).thenReturn(200); + + NicTO to = service.toNicTO(nic, profile, network); + + assertEquals(nic.getDeviceId(), to.getDeviceId()); + assertEquals(BroadcastDomainType.Vlan, to.getBroadcastType()); + assertEquals(TrafficType.Guest, to.getType()); + assertEquals("10.2.2.10", to.getIp()); + assertEquals("255.255.255.0", to.getNetmask()); + assertEquals("02:00:00:00:00:20", to.getMac()); + assertEquals("8.8.8.8", to.getDns1()); + assertEquals("8.8.4.4", to.getDns2()); + assertEquals("10.2.2.1", to.getGateway()); + assertTrue(to.isDefaultNic()); + assertEquals(nic.getBroadcastUri(), to.getBroadcastUri()); + assertEquals(nic.getIsolationUri(), to.getIsolationUri()); + assertEquals(Integer.valueOf(200), to.getNetworkRateMbps()); + assertEquals(network.getUuid(), to.getUuid()); + assertFalse(to.getPxeDisable()); + } + + @Test + public void toNicTOFallsBackToConfigGatewayAndDisablesPxeForNonUserNic() { + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.DomainRouter); + nic.setIPv4Gateway(null); + NetworkVO network = networkWithFields(NETWORK_ID, TrafficType.Control); + network.setGateway("10.2.2.254"); + NicProfile profile = new NicProfile(); + profile.setIPv4Dns1("4.4.4.4"); + profile.setIPv4Dns2("4.4.8.8"); + + NicTO to = service.toNicTO(nic, profile, network); + + assertEquals("10.2.2.254", to.getGateway()); + assertTrue(to.getPxeDisable()); + } + + @Test + public void getNicProfileForVmUsesBroadcastLookupWhenRequestedBroadcastMatchesIp() { + Network network = mockNetwork(NETWORK_ID); + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + NicProfile requested = new NicProfile(); + requested.setBroadcastUri(uri("vlan://601")); + requested.setIPv4Address("10.3.3.10"); + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.User); + nic.setIPv4Address("10.3.3.10"); + NicProfile expected = new NicProfile(); + when(nicDao.findByNetworkIdInstanceIdAndBroadcastUri(NETWORK_ID, VM_ID, "vlan://601")).thenReturn(nic); + when(networkModel.getNicProfile(vm, NETWORK_ID, "vlan://601")).thenReturn(expected); + + NicProfile result = service.getNicProfileForVm(network, requested, vm); + + assertSame(expected, result); + verify(nicDao).findByNetworkIdInstanceIdAndBroadcastUri(NETWORK_ID, VM_ID, "vlan://601"); + verify(networkModel).getNicProfile(vm, NETWORK_ID, "vlan://601"); + } + + @Test + public void getNicProfileForVmRejectsRequestedBroadcastWhenIpv4Differs() { + Network network = mockNetwork(NETWORK_ID); + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + NicProfile requested = new NicProfile(); + requested.setBroadcastUri(uri("vlan://601")); + requested.setIPv4Address("10.3.3.99"); + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.User); + nic.setIPv4Address("10.3.3.10"); + when(nicDao.findByNetworkIdInstanceIdAndBroadcastUri(NETWORK_ID, VM_ID, "vlan://601")).thenReturn(nic); + + NicProfile result = service.getNicProfileForVm(network, requested, vm); + + assertNull(result); + verify(networkModel, never()).getNicProfile(eq(vm), eq(NETWORK_ID), eq("vlan://601")); + } + + @Test + public void getNicProfileForVmFallsBackToNetworkVmLookupWithoutRequestedBroadcast() { + Network network = mockNetwork(NETWORK_ID); + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.User); + NicProfile expected = new NicProfile(); + when(nicDao.findByNtwkIdAndInstanceId(NETWORK_ID, VM_ID)).thenReturn(nic); + when(networkModel.getNicProfile(vm, NETWORK_ID, null)).thenReturn(expected); + + NicProfile result = service.getNicProfileForVm(network, null, vm); + + assertSame(expected, result); + verify(nicDao).findByNtwkIdAndInstanceId(NETWORK_ID, VM_ID); + verify(networkModel).getNicProfile(vm, NETWORK_ID, null); + } + + @Test + public void getNicProfileDefaultNicReturnsRequestedValueOrFalse() { + NicProfile requested = new NicProfile(); + requested.setDefaultNic(true); + assertTrue(service.getNicProfileDefaultNic(requested)); + + requested.setDefaultNic(false); + assertFalse(service.getNicProfileDefaultNic(requested)); + + assertFalse(service.getNicProfileDefaultNic(null)); + } + + @Test + public void getNicProfilesBuildsProfilesAndLetsGuruUpdateEachProfile() { + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.User); + setEntityId(nic, 77L); + NetworkVO network = networkWithFields(NETWORK_ID, TrafficType.Guest); + when(nicDao.listByVmId(VM_ID)).thenReturn(Collections.singletonList(nic)); + when(networksDao.findById(NETWORK_ID)).thenReturn(network); + when(networkModel.getNetworkRate(NETWORK_ID, VM_ID)).thenReturn(300); + when(networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(true); + when(networkModel.getNetworkTag(HypervisorType.KVM, network)).thenReturn("cloudbr0"); + + List profiles = service.getNicProfiles(VM_ID, HypervisorType.KVM); + + assertEquals(1, profiles.size()); + NicProfile profile = profiles.get(0); + assertEquals(NETWORK_ID, profile.getNetworkId()); + assertEquals(77L, profile.getId()); + assertEquals(Integer.valueOf(300), profile.getNetworkRate()); + assertTrue(profile.isSecurityGroupEnabled()); + assertEquals("cloudbr0", profile.getName()); + ArgumentCaptor profileCaptor = ArgumentCaptor.forClass(NicProfile.class); + verify(guru).updateNicProfile(profileCaptor.capture(), eq(network)); + assertSame(profile, profileCaptor.getValue()); + verify(networkModel).getNetworkRate(NETWORK_ID, VM_ID); + verify(networkModel).isSecurityGroupSupportedInNetwork(network); + verify(networkModel).getNetworkTag(HypervisorType.KVM, network); + } + + @Test + public void getNicProfilesReturnsEmptyListWhenDaoReturnsNull() { + when(nicDao.listByVmId(VM_ID)).thenReturn(null); + + List profiles = service.getNicProfiles(VM_ID, HypervisorType.KVM); + + assertTrue(profiles.isEmpty()); + } + + @Test + public void getNicProfilesVirtualMachineUsesVmIdAndHypervisorType() { + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + NicVO nic = nicWithFields(NETWORK_ID, VM_ID, VirtualMachine.Type.User); + NetworkVO network = networkWithFields(NETWORK_ID, TrafficType.Guest); + when(nicDao.listByVmId(VM_ID)).thenReturn(Collections.singletonList(nic)); + when(networksDao.findById(NETWORK_ID)).thenReturn(network); + + List profiles = service.getNicProfiles(vm); + + assertEquals(1, profiles.size()); + verify(nicDao).listByVmId(VM_ID); + } + + @Test + public void getSystemVMAccessDetailsMapsTrafficTypeAddressesAndRouterName() { + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + when(vm.getInstanceName()).thenReturn("r-101"); + NicVO controlNic = nicWithAddress(301L, VM_ID, "10.4.0.10"); + NicVO guestNic = nicWithAddress(302L, VM_ID, "10.5.0.10"); + NicVO managementNic = nicWithAddress(303L, VM_ID, "10.6.0.10"); + when(nicDao.listByVmId(VM_ID)).thenReturn(Arrays.asList(controlNic, guestNic, managementNic)); + when(networksDao.findById(301L)).thenReturn(networkWithFields(301L, TrafficType.Control)); + when(networksDao.findById(302L)).thenReturn(networkWithFields(302L, TrafficType.Guest)); + when(networksDao.findById(303L)).thenReturn(networkWithFields(303L, TrafficType.Management)); + + Map accessDetails = service.getSystemVMAccessDetails(vm); + + assertEquals("r-101", accessDetails.get(NetworkElementCommand.ROUTER_NAME)); + assertEquals("10.4.0.10", accessDetails.get(NetworkElementCommand.ROUTER_IP)); + assertEquals("10.5.0.10", accessDetails.get(NetworkElementCommand.ROUTER_GUEST_IP)); + assertEquals("10.4.0.10", accessDetails.get(TrafficType.Control.name())); + assertEquals("10.5.0.10", accessDetails.get(TrafficType.Guest.name())); + assertEquals("10.6.0.10", accessDetails.get(TrafficType.Management.name())); + } + + @Test + public void getSystemVMAccessDetailsUsesManagementAddressAsRouterIpWhenControlAddressMissing() { + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + when(vm.getInstanceName()).thenReturn("r-102"); + NicVO managementNic = nicWithAddress(303L, VM_ID, "10.6.0.10"); + when(nicDao.listByVmId(VM_ID)).thenReturn(Collections.singletonList(managementNic)); + when(networksDao.findById(303L)).thenReturn(networkWithFields(303L, TrafficType.Management)); + + Map accessDetails = service.getSystemVMAccessDetails(vm); + + assertEquals("10.6.0.10", accessDetails.get(NetworkElementCommand.ROUTER_IP)); + } + + @Test + public void getSystemVMAccessDetailsSkipsNullProfilesAndMissingNetworks() { + NicProfileLifecycleMappingServiceImpl partialService = new NicProfileLifecycleMappingServiceImpl() { + @Override + public List getNicProfiles(final VirtualMachine vm) { + NicProfile profile = new NicProfile(); + profile.setNetworkId(NETWORK_ID); + profile.setIPv4Address("10.7.0.10"); + return Arrays.asList(null, profile); + } + }; + partialService.networksDao = networksDao; + VirtualMachine vm = mockVm(VM_ID, HypervisorType.KVM); + when(vm.getInstanceName()).thenReturn("r-103"); + when(networksDao.findById(NETWORK_ID)).thenReturn(null); + + Map accessDetails = partialService.getSystemVMAccessDetails(vm); + + assertEquals(Collections.singletonMap(NetworkElementCommand.ROUTER_NAME, "r-103"), accessDetails); + } + + private NicProfile profileWithAllocatedFields() { + NicProfile profile = new NicProfile(); + profile.setReservationStrategy(Nic.ReservationStrategy.Create); + profile.setDefaultNic(true); + profile.setIPv4Address("10.1.1.10"); + profile.setFormat(AddressFormat.DualStack); + profile.setMacAddress("02:00:00:00:00:10"); + profile.setMode(Mode.Static); + profile.setIPv4Netmask("255.255.255.0"); + profile.setIPv4Gateway("10.1.1.1"); + profile.setBroadcastUri(uri("vlan://101")); + profile.setIsolationUri(uri("vlan://201")); + profile.setIPv6Address("2001:db8::10"); + profile.setIPv6Gateway("2001:db8::1"); + profile.setIPv6Cidr("2001:db8::/64"); + return profile; + } + + private NicVO nicWithFields(long networkId, long vmId, VirtualMachine.Type vmType) { + NicVO nic = new NicVO("reserver", vmId, networkId, vmType); + nic.setDeviceId(4); + nic.setIPv4Address("10.2.2.10"); + nic.setIPv4Netmask("255.255.255.0"); + nic.setIPv4Gateway("10.2.2.1"); + nic.setMacAddress("02:00:00:00:00:20"); + nic.setAddressFormat(AddressFormat.Ip4); + nic.setMode(Mode.Static); + nic.setDefaultNic(true); + nic.setReservationStrategy(Nic.ReservationStrategy.Start); + nic.setBroadcastUri(uri("vlan://102")); + nic.setIsolationUri(uri("vlan://202")); + return nic; + } + + private NicVO nicWithAddress(long networkId, long vmId, String ipv4Address) { + NicVO nic = nicWithFields(networkId, vmId, VirtualMachine.Type.DomainRouter); + nic.setIPv4Address(ipv4Address); + return nic; + } + + private NetworkVO networkWithFields(long networkId, TrafficType trafficType) { + NetworkVO network = new NetworkVO(networkId, trafficType, Mode.Static, BroadcastDomainType.Vlan, 55L, 66L, 77L, networkId, "network-" + networkId, + "display-" + networkId, "example.local", Network.GuestType.Isolated, 88L, 99L, ACLType.Account, false, null, false); + network.setGateway("10.2.2.254"); + network.setBroadcastUri(uri("vlan://102")); + network.setGuruName(GURU_NAME); + return network; + } + + private Network mockNetwork(long networkId) { + Network network = mock(Network.class); + when(network.getId()).thenReturn(networkId); + return network; + } + + private VirtualMachine mockVm(long vmId, HypervisorType hypervisorType) { + VirtualMachine vm = mock(VirtualMachine.class); + when(vm.getId()).thenReturn(vmId); + when(vm.getHypervisorType()).thenReturn(hypervisorType); + return vm; + } + + private void setEntityId(NicVO nic, long id) { + ReflectionTestUtils.setField(nic, "id", id); + } + + private URI uri(String value) { + return URI.create(value); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuServiceTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuServiceTest.java new file mode 100644 index 000000000000..a439f3e3585d --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NicProfileMtuServiceTest.java @@ -0,0 +1,354 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import com.cloud.api.query.dao.DomainRouterJoinDao; +import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.network.Network; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.vpc.VpcVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; + +@RunWith(JUnit4.class) +public class NicProfileMtuServiceTest { + + private NicProfileMtuServiceImpl service; + + private DomainRouterJoinDao routerJoinDao; + private NetworkDao networksDao; + private EntityManager entityManager; + + private static final long ROUTER_ID = 42L; + private static final long NETWORK_ID = 100L; + private static final long VPC_ID = 200L; + + private static final Integer PUBLIC_MTU_NETWORK = 1500; + private static final Integer PUBLIC_MTU_VPC = 1450; + private static final Integer PRIVATE_MTU = 1400; + + @Before + public void setUp() { + service = new NicProfileMtuServiceImpl(); + routerJoinDao = mock(DomainRouterJoinDao.class); + networksDao = mock(NetworkDao.class); + entityManager = mock(EntityManager.class); + service.routerJoinDao = routerJoinDao; + service.networksDao = networksDao; + service.entityManager = entityManager; + } + + private DomainRouterJoinVO mockRouterRow(long networkId, long vpcId) { + DomainRouterJoinVO row = mock(DomainRouterJoinVO.class); + when(row.getNetworkId()).thenReturn(networkId); + when(row.getVpcId()).thenReturn(vpcId); + return row; + } + + // ---------------- getGuestNetworkRouterAndVpcDetails ---------------- + + @Test + public void testGetGuestNetworkRouterAndVpcDetailsReturnsNullWhenBothTrafficTypesEmpty() { + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(new ArrayList<>()); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Public)).thenReturn(new ArrayList<>()); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertNull(result); + verify(routerJoinDao).getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest); + verify(routerJoinDao).getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Public); + } + + @Test + public void testGetGuestNetworkRouterAndVpcDetailsPrefersGuestTrafficType() { + DomainRouterJoinVO guestRow = mockRouterRow(NETWORK_ID, 0L); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(Arrays.asList(guestRow)); + NetworkVO networkVO = mock(NetworkVO.class); + when(networksDao.findById(NETWORK_ID)).thenReturn(networkVO); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertEquals(networkVO, result.first()); + assertNull(result.second()); + verify(routerJoinDao, never()).getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Public); + } + + @Test + public void testGetGuestNetworkRouterAndVpcDetailsFallsBackToPublicTrafficType() { + DomainRouterJoinVO publicRow = mockRouterRow(NETWORK_ID, 0L); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(new ArrayList<>()); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Public)).thenReturn(Arrays.asList(publicRow)); + NetworkVO networkVO = mock(NetworkVO.class); + when(networksDao.findById(NETWORK_ID)).thenReturn(networkVO); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertEquals(networkVO, result.first()); + assertNull(result.second()); + } + + @Test + public void testGetGuestNetworkRouterAndVpcDetailsResolvesVpcWhenAssigned() { + DomainRouterJoinVO guestRow = mockRouterRow(NETWORK_ID, VPC_ID); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(Arrays.asList(guestRow)); + NetworkVO networkVO = mock(NetworkVO.class); + when(networksDao.findById(NETWORK_ID)).thenReturn(networkVO); + VpcVO vpcVO = mock(VpcVO.class); + when(entityManager.findById(eq(VpcVO.class), eq(VPC_ID))).thenReturn(vpcVO); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertEquals(networkVO, result.first()); + assertEquals(vpcVO, result.second()); + } + + @Test + public void testGetGuestNetworkRouterAndVpcDetailsUsesFirstRowWhenMultiple() { + DomainRouterJoinVO first = mockRouterRow(NETWORK_ID, 0L); + DomainRouterJoinVO second = mockRouterRow(999L, 0L); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(Arrays.asList(first, second)); + NetworkVO networkVO = mock(NetworkVO.class); + when(networksDao.findById(NETWORK_ID)).thenReturn(networkVO); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertEquals(networkVO, result.first()); + verify(networksDao).findById(NETWORK_ID); + verify(networksDao, never()).findById(999L); + } + + // ---------------- setMtuDetailsInVRNic ---------------- + + @Test + public void testSetMtuDetailsInVRNicPublicWithVpcUsesVpcMtu() { + Network network = mock(Network.class); + when(network.getTrafficType()).thenReturn(TrafficType.Public); + NicVO vo = mock(NicVO.class); + NetworkVO networkVO = mock(NetworkVO.class); + VpcVO vpcVO = mock(VpcVO.class); + when(vpcVO.getPublicMtu()).thenReturn(PUBLIC_MTU_VPC); + Pair networks = new Pair<>(networkVO, vpcVO); + + service.setMtuDetailsInVRNic(networks, network, vo); + + verify(vo).setMtu(PUBLIC_MTU_VPC); + } + + @Test + public void testSetMtuDetailsInVRNicPublicWithoutVpcUsesNetworkMtu() { + Network network = mock(Network.class); + when(network.getTrafficType()).thenReturn(TrafficType.Public); + NicVO vo = mock(NicVO.class); + NetworkVO networkVO = mock(NetworkVO.class); + when(networkVO.getPublicMtu()).thenReturn(PUBLIC_MTU_NETWORK); + Pair networks = new Pair<>(networkVO, null); + + service.setMtuDetailsInVRNic(networks, network, vo); + + verify(vo).setMtu(PUBLIC_MTU_NETWORK); + } + + @Test + public void testSetMtuDetailsInVRNicPublicWithNullPairIsNoOp() { + Network network = mock(Network.class); + when(network.getTrafficType()).thenReturn(TrafficType.Public); + NicVO vo = mock(NicVO.class); + + service.setMtuDetailsInVRNic(null, network, vo); + + verify(vo, never()).setMtu(org.mockito.ArgumentMatchers.anyInt()); + } + + @Test + public void testSetMtuDetailsInVRNicGuestUsesNetworkPrivateMtu() { + Network network = mock(Network.class); + when(network.getTrafficType()).thenReturn(TrafficType.Guest); + when(network.getPrivateMtu()).thenReturn(PRIVATE_MTU); + NicVO vo = mock(NicVO.class); + + // null pair is fine for the Guest path -- it short-circuits on network only + service.setMtuDetailsInVRNic(null, network, vo); + + verify(vo).setMtu(PRIVATE_MTU); + } + + @Test + public void testSetMtuDetailsInVRNicNonPublicNonGuestIsNoOp() { + Network network = mock(Network.class); + when(network.getTrafficType()).thenReturn(TrafficType.Management); + NicVO vo = mock(NicVO.class); + Pair networks = new Pair<>(mock(NetworkVO.class), null); + + service.setMtuDetailsInVRNic(networks, network, vo); + + verify(vo, never()).setMtu(org.mockito.ArgumentMatchers.anyInt()); + } + + // ---------------- setMtuInVRNicProfile ---------------- + + @Test + public void testSetMtuInVRNicProfileNullPairIsNoOp() { + NicProfile profile = mock(NicProfile.class); + + service.setMtuInVRNicProfile(null, TrafficType.Guest, profile); + + verify(profile, never()).setMtu(org.mockito.ArgumentMatchers.anyInt()); + } + + @Test + public void testSetMtuInVRNicProfileNullFirstIsNoOp() { + NicProfile profile = mock(NicProfile.class); + Pair networks = new Pair<>(null, mock(VpcVO.class)); + + service.setMtuInVRNicProfile(networks, TrafficType.Public, profile); + + verify(profile, never()).setMtu(org.mockito.ArgumentMatchers.anyInt()); + } + + @Test + public void testSetMtuInVRNicProfilePublicWithVpcUsesVpcMtu() { + NicProfile profile = mock(NicProfile.class); + NetworkVO networkVO = mock(NetworkVO.class); + VpcVO vpcVO = mock(VpcVO.class); + when(vpcVO.getPublicMtu()).thenReturn(PUBLIC_MTU_VPC); + Pair networks = new Pair<>(networkVO, vpcVO); + + service.setMtuInVRNicProfile(networks, TrafficType.Public, profile); + + verify(profile).setMtu(PUBLIC_MTU_VPC); + } + + @Test + public void testSetMtuInVRNicProfilePublicWithoutVpcUsesNetworkPublicMtu() { + NicProfile profile = mock(NicProfile.class); + NetworkVO networkVO = mock(NetworkVO.class); + when(networkVO.getPublicMtu()).thenReturn(PUBLIC_MTU_NETWORK); + Pair networks = new Pair<>(networkVO, null); + + service.setMtuInVRNicProfile(networks, TrafficType.Public, profile); + + verify(profile).setMtu(PUBLIC_MTU_NETWORK); + } + + @Test + public void testSetMtuInVRNicProfileGuestUsesNetworkPrivateMtu() { + NicProfile profile = mock(NicProfile.class); + NetworkVO networkVO = mock(NetworkVO.class); + when(networkVO.getPrivateMtu()).thenReturn(PRIVATE_MTU); + Pair networks = new Pair<>(networkVO, null); + + service.setMtuInVRNicProfile(networks, TrafficType.Guest, profile); + + verify(profile).setMtu(PRIVATE_MTU); + } + + @Test + public void testSetMtuInVRNicProfileOtherTrafficTypeIsNoOp() { + NicProfile profile = mock(NicProfile.class); + NetworkVO networkVO = mock(NetworkVO.class); + Pair networks = new Pair<>(networkVO, null); + + service.setMtuInVRNicProfile(networks, TrafficType.Storage, profile); + + verify(profile, never()).setMtu(org.mockito.ArgumentMatchers.anyInt()); + } + + @Test + public void testCompoundFlowMimicsAllocateNicCallSite() { + // Mirror the sequence the orchestrator uses in allocateNic for a domain router. + DomainRouterJoinVO guestRow = mockRouterRow(NETWORK_ID, VPC_ID); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(Arrays.asList(guestRow)); + NetworkVO networkVO = mock(NetworkVO.class); + when(networkVO.getPublicMtu()).thenReturn(PUBLIC_MTU_NETWORK); + when(networksDao.findById(NETWORK_ID)).thenReturn(networkVO); + VpcVO vpcVO = mock(VpcVO.class); + when(vpcVO.getPublicMtu()).thenReturn(PUBLIC_MTU_VPC); + when(entityManager.findById(eq(VpcVO.class), eq(VPC_ID))).thenReturn(vpcVO); + + Network network = mock(Network.class); + when(network.getTrafficType()).thenReturn(TrafficType.Public); + NicVO vo = mock(NicVO.class); + NicProfile profile = mock(NicProfile.class); + + Pair networks = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + service.setMtuDetailsInVRNic(networks, network, vo); + service.setMtuInVRNicProfile(networks, network.getTrafficType(), profile); + + verify(vo).setMtu(PUBLIC_MTU_VPC); + verify(profile).setMtu(PUBLIC_MTU_VPC); + } + + @Test + public void testGetGuestNetworkRouterAndVpcDetailsDoesNotLookupVpcWhenIdZero() { + DomainRouterJoinVO row = mockRouterRow(NETWORK_ID, 0L); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(Arrays.asList(row)); + when(networksDao.findById(NETWORK_ID)).thenReturn(mock(NetworkVO.class)); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertNull(result.second()); + verify(entityManager, never()).findById(eq(VpcVO.class), org.mockito.ArgumentMatchers.anyLong()); + } + + @Test + public void testEmptyListThenMultiplePublicEntriesReturnsFirst() { + // Empty guest, multi public; takes index 0. + DomainRouterJoinVO first = mockRouterRow(NETWORK_ID, 0L); + DomainRouterJoinVO second = mockRouterRow(888L, 0L); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(new ArrayList<>()); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Public)).thenReturn(Arrays.asList(first, second)); + NetworkVO networkVO = mock(NetworkVO.class); + when(networksDao.findById(NETWORK_ID)).thenReturn(networkVO); + + Pair result = service.getGuestNetworkRouterAndVpcDetails(ROUTER_ID); + + assertEquals(networkVO, result.first()); + } + + @Test + public void testInterfaceContractIsImplementedByImpl() { + // Defensive: ensure the impl is wired through the interface, matching the + // injection pattern used by NetworkOrchestrator. + NicProfileMtuService asInterface = service; + List empty = new ArrayList<>(); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Guest)).thenReturn(empty); + when(routerJoinDao.getRouterByIdAndTrafficType(ROUTER_ID, TrafficType.Public)).thenReturn(empty); + + assertNull(asInterface.getGuestNetworkRouterAndVpcDetails(ROUTER_ID)); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupServiceImplTest.java new file mode 100644 index 000000000000..eced95750984 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/PersistentNetworkSetupServiceImplTest.java @@ -0,0 +1,209 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.SetupPersistentNetworkAnswer; +import com.cloud.agent.api.SetupPersistentNetworkCommand; +import com.cloud.agent.api.to.NicTO; +import com.cloud.configuration.ConfigurationManager; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.NetworkModel; +import com.cloud.network.Networks.BroadcastDomainType; +import com.cloud.network.Networks.TrafficType; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offerings.NetworkOfferingVO; +import com.cloud.resource.ResourceManager; + +public class PersistentNetworkSetupServiceImplTest { + + private static final long DC_ID = 11L; + private static final long OFFERING_ID = 22L; + private static final long CLUSTER_ID = 33L; + private static final long FIRST_HOST_ID = 44L; + private static final long SECOND_HOST_ID = 55L; + private static final URI BROADCAST_URI = URI.create("vlan://101"); + + private PersistentNetworkSetupServiceImpl service; + private ClusterDao clusterDao; + private ResourceManager resourceManager; + private NetworkModel networkModel; + private ConfigurationManager configurationManager; + private AgentManager agentManager; + private NetworkVO network; + private NetworkOfferingVO offering; + + @Before + public void setUp() { + service = new PersistentNetworkSetupServiceImpl(); + clusterDao = mock(ClusterDao.class); + resourceManager = mock(ResourceManager.class); + networkModel = mock(NetworkModel.class); + configurationManager = mock(ConfigurationManager.class); + agentManager = mock(AgentManager.class); + network = mock(NetworkVO.class); + offering = mock(NetworkOfferingVO.class); + + service.clusterDao = clusterDao; + service.resourceManager = resourceManager; + service.networkModel = networkModel; + service.configurationManager = configurationManager; + service.agentManager = agentManager; + + when(network.getBroadcastDomainType()).thenReturn(BroadcastDomainType.Vlan); + when(network.getBroadcastUri()).thenReturn(BROADCAST_URI); + when(network.getDataCenterId()).thenReturn(DC_ID); + when(network.getTrafficType()).thenReturn(TrafficType.Guest); + when(offering.getId()).thenReturn(OFFERING_ID); + when(clusterDao.listClustersByDcId(DC_ID)).thenReturn(Collections.singletonList(mock(ClusterVO.class))); + when(configurationManager.getNetworkOfferingNetworkRate(OFFERING_ID, DC_ID)).thenReturn(200); + when(networkModel.isSecurityGroupSupportedInNetwork(network)).thenReturn(true); + } + + @Test + public void setupPersistentNetworkSendsCommandWithNicDetails() throws AgentUnavailableException, OperationTimedoutException { + HostVO host = host(FIRST_HOST_ID, CLUSTER_ID, HypervisorType.KVM); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, DC_ID)).thenReturn(Collections.singletonList(host)); + when(networkModel.getNetworkTag(HypervisorType.KVM, network)).thenReturn("cloudbr-test"); + stubSuccessfulAnswer(FIRST_HOST_ID); + ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(SetupPersistentNetworkCommand.class); + + service.setupPersistentNetwork(network, offering, DC_ID); + + verify(agentManager).send(eq(FIRST_HOST_ID), commandCaptor.capture()); + NicTO nic = commandCaptor.getValue().getNic(); + assertEquals("cloudbr-test", nic.getName()); + assertEquals(BroadcastDomainType.Vlan, nic.getBroadcastType()); + assertEquals(TrafficType.Guest, nic.getType()); + assertSame(BROADCAST_URI, nic.getBroadcastUri()); + assertSame(BROADCAST_URI, nic.getIsolationUri()); + assertEquals(Integer.valueOf(200), nic.getNetworkRateMbps()); + assertTrue(nic.isSecurityGroupEnabled()); + } + + @Test + public void setupPersistentNetworkConfiguresEveryKvmAndXenHostInSameCluster() throws AgentUnavailableException, OperationTimedoutException { + HostVO kvmHost = host(FIRST_HOST_ID, CLUSTER_ID, HypervisorType.KVM); + HostVO xenHost = host(SECOND_HOST_ID, CLUSTER_ID, HypervisorType.XenServer); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, DC_ID)).thenReturn(Arrays.asList(kvmHost, xenHost)); + stubSuccessfulAnswer(FIRST_HOST_ID); + stubSuccessfulAnswer(SECOND_HOST_ID); + + service.setupPersistentNetwork(network, offering, DC_ID); + + verify(agentManager).send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class)); + verify(agentManager).send(eq(SECOND_HOST_ID), any(SetupPersistentNetworkCommand.class)); + } + + @Test + public void setupPersistentNetworkSkipsSecondNonKvmXenHostInSameCluster() throws AgentUnavailableException, OperationTimedoutException { + HostVO firstHost = host(FIRST_HOST_ID, CLUSTER_ID, HypervisorType.VMware); + HostVO secondHost = host(SECOND_HOST_ID, CLUSTER_ID, HypervisorType.VMware); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, DC_ID)).thenReturn(Arrays.asList(firstHost, secondHost)); + stubSuccessfulAnswer(FIRST_HOST_ID); + + service.setupPersistentNetwork(network, offering, DC_ID); + + verify(agentManager).send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class)); + verify(agentManager, times(0)).send(eq(SECOND_HOST_ID), any(SetupPersistentNetworkCommand.class)); + } + + @Test + public void setupPersistentNetworkRetriesSameClusterWhenFirstAnswerIsNull() throws AgentUnavailableException, OperationTimedoutException { + HostVO firstHost = host(FIRST_HOST_ID, CLUSTER_ID, HypervisorType.VMware); + HostVO secondHost = host(SECOND_HOST_ID, CLUSTER_ID, HypervisorType.VMware); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, DC_ID)).thenReturn(Arrays.asList(firstHost, secondHost)); + when(agentManager.send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class))).thenReturn(null); + stubSuccessfulAnswer(SECOND_HOST_ID); + + service.setupPersistentNetwork(network, offering, DC_ID); + + verify(agentManager).send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class)); + verify(agentManager).send(eq(SECOND_HOST_ID), any(SetupPersistentNetworkCommand.class)); + } + + @Test + public void setupPersistentNetworkRetriesSameClusterWhenFirstAnswerFails() throws AgentUnavailableException, OperationTimedoutException { + HostVO firstHost = host(FIRST_HOST_ID, CLUSTER_ID, HypervisorType.VMware); + HostVO secondHost = host(SECOND_HOST_ID, CLUSTER_ID, HypervisorType.VMware); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, DC_ID)).thenReturn(Arrays.asList(firstHost, secondHost)); + when(agentManager.send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class))).thenAnswer(invocation -> { + SetupPersistentNetworkCommand command = invocation.getArgument(1); + return new SetupPersistentNetworkAnswer(command, false, "bad"); + }); + stubSuccessfulAnswer(SECOND_HOST_ID); + + service.setupPersistentNetwork(network, offering, DC_ID); + + verify(agentManager).send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class)); + verify(agentManager).send(eq(SECOND_HOST_ID), any(SetupPersistentNetworkCommand.class)); + } + + @Test + public void setupPersistentNetworkContinuesWhenAgentSendThrows() throws AgentUnavailableException, OperationTimedoutException { + HostVO firstHost = host(FIRST_HOST_ID, CLUSTER_ID, HypervisorType.KVM); + HostVO secondHost = host(SECOND_HOST_ID, 66L, HypervisorType.KVM); + when(clusterDao.listClustersByDcId(DC_ID)).thenReturn(Arrays.asList(mock(ClusterVO.class), mock(ClusterVO.class))); + when(resourceManager.listAllUpAndEnabledHostsInOneZoneByType(Host.Type.Routing, DC_ID)).thenReturn(Arrays.asList(firstHost, secondHost)); + when(agentManager.send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class))).thenThrow(new RuntimeException("boom")); + stubSuccessfulAnswer(SECOND_HOST_ID); + + service.setupPersistentNetwork(network, offering, DC_ID); + + verify(agentManager).send(eq(FIRST_HOST_ID), any(SetupPersistentNetworkCommand.class)); + verify(agentManager).send(eq(SECOND_HOST_ID), any(SetupPersistentNetworkCommand.class)); + } + + private HostVO host(long id, long clusterId, HypervisorType hypervisorType) { + HostVO host = mock(HostVO.class); + when(host.getId()).thenReturn(id); + when(host.getClusterId()).thenReturn(clusterId); + when(host.getHypervisorType()).thenReturn(hypervisorType); + return host; + } + + private void stubSuccessfulAnswer(long hostId) throws AgentUnavailableException, OperationTimedoutException { + when(agentManager.send(eq(hostId), any(SetupPersistentNetworkCommand.class))).thenAnswer(invocation -> { + SetupPersistentNetworkCommand command = invocation.getArgument(1); + return new SetupPersistentNetworkAnswer(command, true, "ok"); + }); + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationServiceImplTest.java new file mode 100644 index 000000000000..dc0dabf8c105 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/RequestedNicIpReservationServiceImplTest.java @@ -0,0 +1,301 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.VlanDao; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.IpAddress.State; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.Ip; +import com.cloud.vm.NicProfile; + +public class RequestedNicIpReservationServiceImplTest { + + private static final long NETWORK_ID = 101L; + private static final long IP_ID = 202L; + private static final String REQUESTED_IPV4 = "192.168.100.150"; + private static final String GATEWAY = "192.168.100.1"; + private static final String NETMASK = "255.255.255.0"; + private static final String MAC = "00-88-14-4D-4C-FB"; + private static final String NEXT_MAC = "02:00:00:00:00:01"; + + private RequestedNicIpReservationServiceImpl service; + private VlanDao vlanDao; + private IPAddressDao ipAddressDao; + private NetworkModel networkModel; + private Network network; + + @Before + public void setUp() { + service = new RequestedNicIpReservationServiceImpl(); + vlanDao = mock(VlanDao.class); + ipAddressDao = mock(IPAddressDao.class); + networkModel = mock(NetworkModel.class); + service.vlanDao = vlanDao; + service.ipAddressDao = ipAddressDao; + service.networkModel = networkModel; + + network = mock(Network.class); + when(network.getId()).thenReturn(NETWORK_ID); + } + + @Test + public void configureNicProfileBasedOnRequestedIpAssignsAddressGatewayNetmaskAndMacWhenMacMissing() throws Exception { + NicProfile requested = requestedProfile(REQUESTED_IPV4); + NicProfile profile = new NicProfile(); + IPAddressVO ip = stubVlanAndFreeIp(GATEWAY, NETMASK); + when(networkModel.getNextAvailableMacAddressInNetwork(NETWORK_ID)).thenReturn(NEXT_MAC); + + service.configureNicProfileBasedOnRequestedIp(requested, profile, network); + + assertEquals(REQUESTED_IPV4, profile.getIPv4Address()); + assertEquals(GATEWAY, profile.getIPv4Gateway()); + assertEquals(NETMASK, profile.getIPv4Netmask()); + assertEquals(NEXT_MAC, profile.getMacAddress()); + assertEquals(State.Allocated, ip.getState()); + assertNotNull(ip.getAllocatedTime()); + verify(ipAddressDao).update(IP_ID, ip); + verify(ipAddressDao).releaseFromLockTable(IP_ID); + } + + @Test + public void configureNicProfileBasedOnRequestedIpKeepsUniqueExistingMac() throws Exception { + NicProfile requested = requestedProfile(REQUESTED_IPV4); + NicProfile profile = new NicProfile(); + profile.setMacAddress(MAC); + stubVlanAndFreeIp(GATEWAY, NETMASK); + when(networkModel.isMACUnique(MAC, NETWORK_ID)).thenReturn(true); + + service.configureNicProfileBasedOnRequestedIp(requested, profile, network); + + assertEquals(MAC, profile.getMacAddress()); + verify(networkModel, never()).getNextAvailableMacAddressInNetwork(anyLong()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpReplacesDuplicateExistingMac() throws Exception { + NicProfile requested = requestedProfile(REQUESTED_IPV4); + NicProfile profile = new NicProfile(); + profile.setMacAddress(MAC); + stubVlanAndFreeIp(GATEWAY, NETMASK); + when(networkModel.isMACUnique(MAC, NETWORK_ID)).thenReturn(false); + when(networkModel.getNextAvailableMacAddressInNetwork(NETWORK_ID)).thenReturn(NEXT_MAC); + + service.configureNicProfileBasedOnRequestedIp(requested, profile, network); + + assertEquals(NEXT_MAC, profile.getMacAddress()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpReturnsWhenRequestedProfileIsNull() { + service.configureNicProfileBasedOnRequestedIp(null, new NicProfile(), network); + + verify(vlanDao, never()).findByNetworkIdAndIpv4(anyLong(), anyString()); + verify(ipAddressDao, never()).findByIpAndSourceNetworkId(anyLong(), anyString()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpReturnsWhenRequestedIpv4IsNull() { + service.configureNicProfileBasedOnRequestedIp(new NicProfile(), new NicProfile(), network); + + verify(vlanDao, never()).findByNetworkIdAndIpv4(anyLong(), anyString()); + verify(ipAddressDao, never()).findByIpAndSourceNetworkId(anyLong(), anyString()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpRejectsInvalidRequestedIpv4() { + InvalidParameterValueException exception = assertThrows(InvalidParameterValueException.class, + () -> service.configureNicProfileBasedOnRequestedIp(requestedProfile("123"), new NicProfile(), network)); + + assertEquals("The requested [IPv4 address='123'] is not a valid IP address", exception.getMessage()); + verify(vlanDao, never()).findByNetworkIdAndIpv4(anyLong(), anyString()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpRejectsMissingVlan() { + when(vlanDao.findByNetworkIdAndIpv4(NETWORK_ID, REQUESTED_IPV4)).thenReturn(null); + + assertThrows(InvalidParameterValueException.class, + () -> service.configureNicProfileBasedOnRequestedIp(requestedProfile(REQUESTED_IPV4), new NicProfile(), network)); + verify(ipAddressDao, never()).findByIpAndSourceNetworkId(anyLong(), anyString()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpRejectsInvalidGatewayBeforeLockingIp() { + VlanVO vlan = vlan("123", NETMASK); + when(vlanDao.findByNetworkIdAndIpv4(NETWORK_ID, REQUESTED_IPV4)).thenReturn(vlan); + + assertThrows(InvalidParameterValueException.class, + () -> service.configureNicProfileBasedOnRequestedIp(requestedProfile(REQUESTED_IPV4), new NicProfile(), network)); + verify(ipAddressDao, never()).findByIpAndSourceNetworkId(anyLong(), anyString()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpRejectsInvalidNetmaskBeforeLockingIp() { + VlanVO vlan = vlan(GATEWAY, "123"); + when(vlanDao.findByNetworkIdAndIpv4(NETWORK_ID, REQUESTED_IPV4)).thenReturn(vlan); + + assertThrows(InvalidParameterValueException.class, + () -> service.configureNicProfileBasedOnRequestedIp(requestedProfile(REQUESTED_IPV4), new NicProfile(), network)); + verify(ipAddressDao, never()).findByIpAndSourceNetworkId(anyLong(), anyString()); + } + + @Test + public void configureNicProfileBasedOnRequestedIpWrapsMacCapacityFailure() throws Exception { + stubVlanAndFreeIp(GATEWAY, NETMASK); + when(networkModel.getNextAvailableMacAddressInNetwork(NETWORK_ID)).thenThrow(new InsufficientAddressCapacityException("no mac", Network.class, NETWORK_ID)); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + () -> service.configureNicProfileBasedOnRequestedIp(requestedProfile(REQUESTED_IPV4), new NicProfile(), network)); + + assertEquals("Cannot get next available mac address in [network " + network + "]", exception.getMessage()); + } + + @Test + public void acquireLockAndCheckIfIpv4IsFreeAllocatesAndReleasesFreeIp() { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Free); + when(ipAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, REQUESTED_IPV4)).thenReturn(ip); + when(ipAddressDao.acquireInLockTable(IP_ID)).thenReturn(ip); + when(ipAddressDao.update(IP_ID, ip)).thenReturn(true); + when(ipAddressDao.releaseFromLockTable(IP_ID)).thenReturn(true); + + service.acquireLockAndCheckIfIpv4IsFree(network, REQUESTED_IPV4); + + assertEquals(State.Allocated, ip.getState()); + assertNotNull(ip.getAllocatedTime()); + verify(ipAddressDao).update(IP_ID, ip); + verify(ipAddressDao).releaseFromLockTable(IP_ID); + } + + @Test + public void acquireLockAndCheckIfIpv4IsFreeThrowsWithoutReleaseWhenIpVoMissing() { + when(ipAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, REQUESTED_IPV4)).thenReturn(null); + + assertThrows(InvalidParameterValueException.class, () -> service.acquireLockAndCheckIfIpv4IsFree(network, REQUESTED_IPV4)); + + verify(ipAddressDao, never()).acquireInLockTable(anyLong()); + verify(ipAddressDao, never()).releaseFromLockTable(anyLong()); + } + + @Test + public void acquireLockAndCheckIfIpv4IsFreeReleasesWhenLockedIpMissing() { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Free); + when(ipAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, REQUESTED_IPV4)).thenReturn(ip); + when(ipAddressDao.acquireInLockTable(IP_ID)).thenReturn(null); + when(ipAddressDao.releaseFromLockTable(IP_ID)).thenReturn(true); + + assertThrows(InvalidParameterValueException.class, () -> service.acquireLockAndCheckIfIpv4IsFree(network, REQUESTED_IPV4)); + + verify(ipAddressDao, never()).update(anyLong(), any(IPAddressVO.class)); + verify(ipAddressDao).releaseFromLockTable(IP_ID); + } + + @Test + public void acquireLockAndCheckIfIpv4IsFreeReleasesAndDoesNotUpdateNonFreeIp() { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Allocated); + when(ipAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, REQUESTED_IPV4)).thenReturn(ip); + when(ipAddressDao.acquireInLockTable(IP_ID)).thenReturn(ip); + when(ipAddressDao.releaseFromLockTable(IP_ID)).thenReturn(true); + + assertThrows(InvalidParameterValueException.class, () -> service.acquireLockAndCheckIfIpv4IsFree(network, REQUESTED_IPV4)); + + verify(ipAddressDao, never()).update(anyLong(), any(IPAddressVO.class)); + verify(ipAddressDao).releaseFromLockTable(IP_ID); + } + + @Test + public void validateLockedRequestedIpRejectsNullLockedIp() { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Free); + + InvalidParameterValueException exception = assertThrows(InvalidParameterValueException.class, () -> service.validateLockedRequestedIp(ip, null)); + + assertEquals("Cannot acquire guest [IPv4 address='" + REQUESTED_IPV4 + "'] as it was removed while acquiring lock", exception.getMessage()); + } + + @Test + public void validateLockedRequestedIpRejectsEveryNonFreeState() { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Free); + for (State state : State.values()) { + if (state == State.Free) { + continue; + } + IPAddressVO lockedIp = ipAddress(REQUESTED_IPV4, state); + assertThrows(InvalidParameterValueException.class, () -> service.validateLockedRequestedIp(ip, lockedIp)); + } + } + + @Test + public void validateLockedRequestedIpAcceptsFreeState() { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Free); + + service.validateLockedRequestedIp(ip, ip); + } + + private IPAddressVO stubVlanAndFreeIp(String gateway, String netmask) { + IPAddressVO ip = ipAddress(REQUESTED_IPV4, State.Free); + VlanVO vlan = vlan(gateway, netmask); + when(vlanDao.findByNetworkIdAndIpv4(NETWORK_ID, REQUESTED_IPV4)).thenReturn(vlan); + when(ipAddressDao.findByIpAndSourceNetworkId(NETWORK_ID, REQUESTED_IPV4)).thenReturn(ip); + when(ipAddressDao.acquireInLockTable(IP_ID)).thenReturn(ip); + when(ipAddressDao.update(IP_ID, ip)).thenReturn(true); + when(ipAddressDao.releaseFromLockTable(IP_ID)).thenReturn(true); + return ip; + } + + private VlanVO vlan(String gateway, String netmask) { + VlanVO vlan = mock(VlanVO.class); + when(vlan.getVlanGateway()).thenReturn(gateway); + when(vlan.getVlanNetmask()).thenReturn(netmask); + when(vlan.getId()).thenReturn(303L); + when(vlan.getUuid()).thenReturn("vlan-uuid"); + return vlan; + } + + private NicProfile requestedProfile(String requestedIpv4) { + NicProfile profile = new NicProfile(); + profile.setRequestedIPv4(requestedIpv4); + return profile; + } + + private IPAddressVO ipAddress(String address, State state) { + IPAddressVO ip = Mockito.spy(new IPAddressVO(new Ip(address), 0L, 0L, 0L, true)); + Mockito.doReturn(IP_ID).when(ip).getId(); + ip.setState(state); + return ip; + } +} diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateServiceImplTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateServiceImplTest.java new file mode 100644 index 000000000000..e981a1808340 --- /dev/null +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/RouterDefaultDnsUpdateServiceImplTest.java @@ -0,0 +1,221 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.engine.orchestration; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.network.dao.NetworkDao; +import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.RouterNetworkDao; +import com.cloud.network.vpc.VpcManager; +import com.cloud.network.vpc.VpcVO; +import com.cloud.vm.DomainRouterVO; +import com.cloud.vm.NicProfile; +import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.DomainRouterDao; + +@RunWith(MockitoJUnitRunner.class) +public class RouterDefaultDnsUpdateServiceImplTest { + + private static final long ROUTER_ID = 101L; + private static final long VPC_ID = 202L; + private static final long ROUTER_NETWORK_ID = 303L; + + private static final String EXISTING_IPV4_DNS1 = "1.1.1.1"; + private static final String EXISTING_IPV4_DNS2 = "1.0.0.1"; + private static final String EXISTING_IPV6_DNS1 = "2606:4700:4700::1111"; + private static final String EXISTING_IPV6_DNS2 = "2606:4700:4700::1001"; + private static final String CUSTOM_IPV4_DNS1 = "5.5.5.5"; + private static final String CUSTOM_IPV4_DNS2 = "6.6.6.6"; + private static final String CUSTOM_IPV6_DNS1 = "2001:4860:4860::5555"; + private static final String CUSTOM_IPV6_DNS2 = "2001:4860:4860::6666"; + + @Mock + private DomainRouterDao routerDao; + @Mock + private RouterNetworkDao routerNetworkDao; + @Mock + private VpcManager vpcManager; + @Mock + private NetworkDao networksDao; + + private RouterDefaultDnsUpdateServiceImpl service; + + @Before + public void setUp() { + service = new RouterDefaultDnsUpdateServiceImpl(); + service.routerDao = routerDao; + service.routerNetworkDao = routerNetworkDao; + service.vpcManager = vpcManager; + service.networksDao = networksDao; + } + + @Test + public void updateRouterDefaultDnsReturnsForNonRouterVm() { + VirtualMachineProfile vmProfile = vmProfile(Type.User); + NicProfile nicProfile = defaultProfile(); + + service.updateRouterDefaultDns(vmProfile, nicProfile); + + assertExistingDns(nicProfile); + verifyNoInteractions(routerDao, routerNetworkDao, vpcManager, networksDao); + } + + @Test + public void updateRouterDefaultDnsReturnsForNonDefaultRouterNic() { + VirtualMachineProfile vmProfile = vmProfile(Type.DomainRouter); + NicProfile nicProfile = profile(false); + + service.updateRouterDefaultDns(vmProfile, nicProfile); + + assertExistingDns(nicProfile); + verifyNoInteractions(routerDao, routerNetworkDao, vpcManager, networksDao); + } + + @Test + public void updateRouterDefaultDnsUsesVpcCustomDns() { + DomainRouterVO router = router(VPC_ID); + when(routerDao.findById(ROUTER_ID)).thenReturn(router); + VpcVO vpc = mock(VpcVO.class); + when(vpc.getIp4Dns1()).thenReturn(CUSTOM_IPV4_DNS1); + when(vpc.getIp4Dns2()).thenReturn(CUSTOM_IPV4_DNS2); + when(vpc.getIp6Dns1()).thenReturn(CUSTOM_IPV6_DNS1); + when(vpc.getIp6Dns2()).thenReturn(CUSTOM_IPV6_DNS2); + when(vpcManager.getActiveVpc(VPC_ID)).thenReturn(vpc); + NicProfile nicProfile = defaultProfile(); + + service.updateRouterDefaultDns(vmProfile(Type.DomainRouter), nicProfile); + + assertCustomDns(nicProfile); + verifyNoInteractions(routerNetworkDao, networksDao); + } + + @Test + public void updateRouterDefaultDnsLeavesExistingDnsWhenVpcCustomDnsIsBlank() { + DomainRouterVO router = router(VPC_ID); + when(routerDao.findById(ROUTER_ID)).thenReturn(router); + VpcVO vpc = mock(VpcVO.class); + when(vpc.getIp4Dns1()).thenReturn(""); + when(vpc.getIp6Dns1()).thenReturn(null); + when(vpcManager.getActiveVpc(VPC_ID)).thenReturn(vpc); + NicProfile nicProfile = defaultProfile(); + + service.updateRouterDefaultDns(vmProfile(Type.DomainRouter), nicProfile); + + assertExistingDns(nicProfile); + verifyNoInteractions(routerNetworkDao, networksDao); + } + + @Test + public void updateRouterDefaultDnsUsesSingleRouterNetworkCustomDns() { + DomainRouterVO router = router(null); + when(routerDao.findById(ROUTER_ID)).thenReturn(router); + when(routerNetworkDao.getRouterNetworks(ROUTER_ID)).thenReturn(List.of(ROUTER_NETWORK_ID)); + NetworkVO routerNetwork = mock(NetworkVO.class); + when(routerNetwork.getDns1()).thenReturn(CUSTOM_IPV4_DNS1); + when(routerNetwork.getDns2()).thenReturn(CUSTOM_IPV4_DNS2); + when(routerNetwork.getIp6Dns1()).thenReturn(CUSTOM_IPV6_DNS1); + when(routerNetwork.getIp6Dns2()).thenReturn(CUSTOM_IPV6_DNS2); + when(networksDao.findById(ROUTER_NETWORK_ID)).thenReturn(routerNetwork); + NicProfile nicProfile = defaultProfile(); + + service.updateRouterDefaultDns(vmProfile(Type.DomainRouter), nicProfile); + + assertCustomDns(nicProfile); + } + + @Test + public void updateRouterDefaultDnsLeavesExistingDnsWhenRouterHasNoNetwork() { + DomainRouterVO router = router(null); + when(routerDao.findById(ROUTER_ID)).thenReturn(router); + when(routerNetworkDao.getRouterNetworks(ROUTER_ID)).thenReturn(Collections.emptyList()); + NicProfile nicProfile = defaultProfile(); + + service.updateRouterDefaultDns(vmProfile(Type.DomainRouter), nicProfile); + + assertExistingDns(nicProfile); + verifyNoInteractions(networksDao); + } + + @Test + public void updateRouterDefaultDnsLeavesExistingDnsWhenRouterHasMultipleNetworks() { + DomainRouterVO router = router(null); + when(routerDao.findById(ROUTER_ID)).thenReturn(router); + when(routerNetworkDao.getRouterNetworks(ROUTER_ID)).thenReturn(Arrays.asList(ROUTER_NETWORK_ID, ROUTER_NETWORK_ID + 1)); + NicProfile nicProfile = defaultProfile(); + + service.updateRouterDefaultDns(vmProfile(Type.DomainRouter), nicProfile); + + assertExistingDns(nicProfile); + verifyNoInteractions(networksDao); + } + + private VirtualMachineProfile vmProfile(Type type) { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + when(vmProfile.getType()).thenReturn(type); + when(vmProfile.getId()).thenReturn(ROUTER_ID); + return vmProfile; + } + + private DomainRouterVO router(Long vpcId) { + DomainRouterVO router = mock(DomainRouterVO.class); + when(router.getVpcId()).thenReturn(vpcId); + return router; + } + + private NicProfile defaultProfile() { + return profile(true); + } + + private NicProfile profile(boolean defaultNic) { + NicProfile nicProfile = new NicProfile(); + nicProfile.setDefaultNic(defaultNic); + nicProfile.setIPv4Dns1(EXISTING_IPV4_DNS1); + nicProfile.setIPv4Dns2(EXISTING_IPV4_DNS2); + nicProfile.setIPv6Dns1(EXISTING_IPV6_DNS1); + nicProfile.setIPv6Dns2(EXISTING_IPV6_DNS2); + return nicProfile; + } + + private void assertExistingDns(NicProfile nicProfile) { + assertEquals(EXISTING_IPV4_DNS1, nicProfile.getIPv4Dns1()); + assertEquals(EXISTING_IPV4_DNS2, nicProfile.getIPv4Dns2()); + assertEquals(EXISTING_IPV6_DNS1, nicProfile.getIPv6Dns1()); + assertEquals(EXISTING_IPV6_DNS2, nicProfile.getIPv6Dns2()); + } + + private void assertCustomDns(NicProfile nicProfile) { + assertEquals(CUSTOM_IPV4_DNS1, nicProfile.getIPv4Dns1()); + assertEquals(CUSTOM_IPV4_DNS2, nicProfile.getIPv4Dns2()); + assertEquals(CUSTOM_IPV6_DNS1, nicProfile.getIPv6Dns1()); + assertEquals(CUSTOM_IPV6_DNS2, nicProfile.getIPv6Dns2()); + } +} diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml index 654cd14a25d3..147ecda8bfcc 100644 --- a/engine/schema/pom.xml +++ b/engine/schema/pom.xml @@ -66,8 +66,8 @@ ${cs.gmavenplus.version} - org.codehaus.groovy - groovy-all + org.apache.groovy + groovy ${cs.groovy.version} diff --git a/engine/schema/src/main/java/com/cloud/alert/AlertVO.java b/engine/schema/src/main/java/com/cloud/alert/AlertVO.java index 1f2cd9d8c601..1ae284652f79 100644 --- a/engine/schema/src/main/java/com/cloud/alert/AlertVO.java +++ b/engine/schema/src/main/java/com/cloud/alert/AlertVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java index fb2d61d8e11a..7d64531f66e1 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java +++ b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java @@ -20,15 +20,15 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java index f65c3cf188cd..857ef6eac02f 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; diff --git a/engine/schema/src/main/java/com/cloud/certificate/CertificateVO.java b/engine/schema/src/main/java/com/cloud/certificate/CertificateVO.java index 11bf8e887807..74b8429fd4fe 100644 --- a/engine/schema/src/main/java/com/cloud/certificate/CertificateVO.java +++ b/engine/schema/src/main/java/com/cloud/certificate/CertificateVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.certificate; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/certificate/CrlVO.java b/engine/schema/src/main/java/com/cloud/certificate/CrlVO.java index 6df7530b2903..c3d954800604 100644 --- a/engine/schema/src/main/java/com/cloud/certificate/CrlVO.java +++ b/engine/schema/src/main/java/com/cloud/certificate/CrlVO.java @@ -20,14 +20,14 @@ import java.math.BigInteger; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/cluster/agentlb/HostTransferMapVO.java b/engine/schema/src/main/java/com/cloud/cluster/agentlb/HostTransferMapVO.java index 8d28b8ee2b81..b0560c94db12 100644 --- a/engine/schema/src/main/java/com/cloud/cluster/agentlb/HostTransferMapVO.java +++ b/engine/schema/src/main/java/com/cloud/cluster/agentlb/HostTransferMapVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/cluster/agentlb/dao/HostTransferMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/cluster/agentlb/dao/HostTransferMapDaoImpl.java index 861dbeb1df4a..33b8298ef1be 100644 --- a/engine/schema/src/main/java/com/cloud/cluster/agentlb/dao/HostTransferMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/cluster/agentlb/dao/HostTransferMapDaoImpl.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/configuration/ResourceCountVO.java b/engine/schema/src/main/java/com/cloud/configuration/ResourceCountVO.java index 9e39a608f9ec..782e01213801 100644 --- a/engine/schema/src/main/java/com/cloud/configuration/ResourceCountVO.java +++ b/engine/schema/src/main/java/com/cloud/configuration/ResourceCountVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.configuration; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "resource_count") diff --git a/engine/schema/src/main/java/com/cloud/configuration/ResourceLimitVO.java b/engine/schema/src/main/java/com/cloud/configuration/ResourceLimitVO.java index 1619537ae744..ba771442e099 100644 --- a/engine/schema/src/main/java/com/cloud/configuration/ResourceLimitVO.java +++ b/engine/schema/src/main/java/com/cloud/configuration/ResourceLimitVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.configuration; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "resource_limit") diff --git a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java index 2083fb422d28..c5604e51d124 100644 --- a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java @@ -17,7 +17,6 @@ package com.cloud.configuration.dao; import java.sql.PreparedStatement; -import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -26,8 +25,8 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ObjectUtils; @@ -329,32 +328,6 @@ public long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType) { return 0; } - private String baseSqlCountComputingResourceAllocatedToAccount = "Select " - + " SUM((CASE " - + " WHEN so.%s is not null THEN so.%s " - + " ELSE CONVERT(vmd.value, UNSIGNED INTEGER) " - + " END)) as total " - + " from vm_instance vm " - + " join service_offering so on so.id = vm.service_offering_id " - + " left join vm_instance_details vmd on vmd.vm_id = vm.id and vmd.name = '%s' " - + " where vm.type = 'User' and state not in ('Destroyed', 'Error', 'Expunging') and display_vm = true and account_id = ? "; - - private long executeSqlCountComputingResourcesForAccount(long accountId, String sqlCountComputingResourcesAllocatedToAccount) { - TransactionLegacy tx = TransactionLegacy.currentTxn(); - try { - PreparedStatement pstmt = tx.prepareAutoCloseStatement(sqlCountComputingResourcesAllocatedToAccount); - pstmt.setLong(1, accountId); - - ResultSet rs = pstmt.executeQuery(); - if (!rs.next()) { - return 0L; - } - return rs.getLong("total"); - } catch (SQLException e) { - throw new CloudRuntimeException(e); - } - } - @Override public void removeResourceCountsForNonMatchingTags(Long ownerId, ResourceOwnerType ownerType, List types, List tags) { SearchCriteria sc = NonMatchingTagsSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/dc/ASNumberRangeVO.java b/engine/schema/src/main/java/com/cloud/dc/ASNumberRangeVO.java index 3790213b3ade..9999595bdef2 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ASNumberRangeVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ASNumberRangeVO.java @@ -19,12 +19,12 @@ import com.cloud.bgp.ASNumberRange; import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.GregorianCalendar; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/dc/ASNumberVO.java b/engine/schema/src/main/java/com/cloud/dc/ASNumberVO.java index 529d1cfb5fe8..35653507948d 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ASNumberVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ASNumberVO.java @@ -19,14 +19,14 @@ import com.cloud.bgp.ASNumber; import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/dc/AccountVlanMapVO.java b/engine/schema/src/main/java/com/cloud/dc/AccountVlanMapVO.java index 6461a9634fd8..52d1e004feac 100644 --- a/engine/schema/src/main/java/com/cloud/dc/AccountVlanMapVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/AccountVlanMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.dc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java index a8888f98ad29..5710ef651087 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java index b213f8f2594b..a9bd571d42c1 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.dc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java b/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java index a18097db6d6c..e66c741ad19a 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java @@ -26,15 +26,15 @@ import org.apache.cloudstack.util.CPUArchConverter; import org.apache.cloudstack.util.HypervisorTypeConverter; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterVSMMapVO.java b/engine/schema/src/main/java/com/cloud/dc/ClusterVSMMapVO.java index d526c5141fa5..dffa920d93c7 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterVSMMapVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterVSMMapVO.java @@ -16,9 +16,9 @@ // under the License. package com.cloud.dc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; // NOTE: This particular table is totally internal to the CS MS. // Do not ever include a uuid/guid field in this table. We just diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterDetailVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterDetailVO.java index 5e08fac58eeb..3ac2cc10195c 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.dc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java index ff48510e4d2c..03f165800499 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterGuestIpv6PrefixVO.java @@ -20,12 +20,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java index 874b05673eb1..d1773199885a 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterIpAddressVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterLinkLocalIpAddressVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterLinkLocalIpAddressVO.java index 31d124c80ef1..d152db12e1e3 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterLinkLocalIpAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterLinkLocalIpAddressVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java index 9b24e51a1a8b..4cf75b6e1b0e 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java @@ -20,16 +20,16 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Transient; import com.cloud.network.Network.Provider; import com.cloud.org.Grouping; diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterVnetVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterVnetVO.java index b2b12bba6863..e2b305dace42 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterVnetVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterVnetVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.network.GuestVlan; diff --git a/engine/schema/src/main/java/com/cloud/dc/DomainVlanMapVO.java b/engine/schema/src/main/java/com/cloud/dc/DomainVlanMapVO.java index 86e29e4bafd5..8d295023dcc7 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DomainVlanMapVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DomainVlanMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.dc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java b/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java index 99ebcf2346c5..1e27f3c619dd 100644 --- a/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.org.Grouping; import com.cloud.utils.NumbersUtil; diff --git a/engine/schema/src/main/java/com/cloud/dc/PodVlanMapVO.java b/engine/schema/src/main/java/com/cloud/dc/PodVlanMapVO.java index 5f84cf28f686..cff7cf7a733d 100644 --- a/engine/schema/src/main/java/com/cloud/dc/PodVlanMapVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/PodVlanMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.dc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/PodVlanVO.java b/engine/schema/src/main/java/com/cloud/dc/PodVlanVO.java index 01668d9701fd..5a89214e3298 100644 --- a/engine/schema/src/main/java/com/cloud/dc/PodVlanVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/PodVlanVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpAddressVO.java b/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpAddressVO.java index d7191428fdb9..1435f5622ef1 100644 --- a/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpAddressVO.java @@ -18,17 +18,17 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.SecondaryTables; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.SecondaryTables; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpRangeVO.java b/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpRangeVO.java index 9b84ac7d23d8..6e311f6ab7b4 100644 --- a/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpRangeVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/StorageNetworkIpRangeVO.java @@ -18,15 +18,15 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.SecondaryTables; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.SecondaryTables; +import jakarta.persistence.Table; @Entity @Table(name = "dc_storage_network_ip_range") diff --git a/engine/schema/src/main/java/com/cloud/dc/VlanDetailsVO.java b/engine/schema/src/main/java/com/cloud/dc/VlanDetailsVO.java index 1a1db3a56814..275d045d4727 100644 --- a/engine/schema/src/main/java/com/cloud/dc/VlanDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/VlanDetailsVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.ResourceDetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "vlan_details") diff --git a/engine/schema/src/main/java/com/cloud/dc/VlanVO.java b/engine/schema/src/main/java/com/cloud/dc/VlanVO.java index c271325f3dee..985492113081 100644 --- a/engine/schema/src/main/java/com/cloud/dc/VlanVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/VlanVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.java b/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.java index 5a4a71f82e7f..913fbf2e15cd 100644 --- a/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/VmwareDatacenterVO.java @@ -19,12 +19,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.NumbersUtil; import com.cloud.utils.db.Encrypt; diff --git a/engine/schema/src/main/java/com/cloud/dc/VsphereStoragePolicyVO.java b/engine/schema/src/main/java/com/cloud/dc/VsphereStoragePolicyVO.java index 5324de63bb35..7d8b3e6e829c 100644 --- a/engine/schema/src/main/java/com/cloud/dc/VsphereStoragePolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/VsphereStoragePolicyVO.java @@ -19,14 +19,14 @@ import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java index c63af0a237ba..fce2e3e69b5a 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java index d8ab12e82e61..a65a5f37f91e 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java @@ -22,7 +22,7 @@ import java.util.Random; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.db.GenericSearchBuilder; diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterVnetDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterVnetDaoImpl.java index ff6682497791..959bdcc32f0d 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterVnetDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterVnetDaoImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java index d9fad3cad12a..61c6715b0d4b 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.dc.VlanDetailsVO; @@ -34,7 +34,6 @@ import com.cloud.dc.AccountVlanMapVO; import com.cloud.dc.DomainVlanMapVO; import com.cloud.dc.PodVlanMapVO; -import com.cloud.dc.Vlan; import com.cloud.dc.Vlan.VlanType; import com.cloud.dc.VlanVO; import com.cloud.network.dao.IPAddressDao; @@ -298,45 +297,6 @@ public boolean configure(String name, Map params) throws Configu return result; } - private VlanVO findNextVlan(long zoneId, Vlan.VlanType vlanType) { - List allVlans = listByZoneAndType(zoneId, vlanType); - List emptyVlans = new ArrayList(); - List fullVlans = new ArrayList(); - - // Try to find a VLAN that is partially allocated - for (VlanVO vlan : allVlans) { - long vlanDbId = vlan.getId(); - - int countOfAllocatedIps = _ipAddressDao.countIPs(zoneId, vlanDbId, true); - int countOfAllIps = _ipAddressDao.countIPs(zoneId, vlanDbId, false); - - if ((countOfAllocatedIps > 0) && (countOfAllocatedIps < countOfAllIps)) { - return vlan; - } else if (countOfAllocatedIps == 0) { - emptyVlans.add(vlan); - } else if (countOfAllocatedIps == countOfAllIps) { - fullVlans.add(vlan); - } - } - - if (emptyVlans.isEmpty()) { - return null; - } - - // Try to find an empty VLAN with the same tag/subnet as a VLAN that is full - for (VlanVO fullVlan : fullVlans) { - for (VlanVO emptyVlan : emptyVlans) { - if (fullVlan.getVlanTag().equals(emptyVlan.getVlanTag()) && fullVlan.getVlanGateway().equals(emptyVlan.getVlanGateway()) && - fullVlan.getVlanNetmask().equals(emptyVlan.getVlanNetmask())) { - return emptyVlan; - } - } - } - - // Return a random empty VLAN - return emptyVlans.get(0); - } - @Override public boolean zoneHasDirectAttachUntaggedVlans(long zoneId) { SearchCriteria sc = ZoneTypeAllPodsSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/deployasis/TemplateDeployAsIsDetailVO.java b/engine/schema/src/main/java/com/cloud/deployasis/TemplateDeployAsIsDetailVO.java index 047d985d0bb6..24dc119e5a46 100644 --- a/engine/schema/src/main/java/com/cloud/deployasis/TemplateDeployAsIsDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/deployasis/TemplateDeployAsIsDetailVO.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.deployasis; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Lob; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/deployasis/UserVmDeployAsIsDetailVO.java b/engine/schema/src/main/java/com/cloud/deployasis/UserVmDeployAsIsDetailVO.java index b56b4a494646..c1ad54371889 100644 --- a/engine/schema/src/main/java/com/cloud/deployasis/UserVmDeployAsIsDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/deployasis/UserVmDeployAsIsDetailVO.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.deployasis; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Lob; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java index 6f803cc9f2ff..ce52ebe16287 100644 --- a/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/domain/DomainDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.domain; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/domain/DomainVO.java b/engine/schema/src/main/java/com/cloud/domain/DomainVO.java index c950fa31c881..b97acbc806ba 100644 --- a/engine/schema/src/main/java/com/cloud/domain/DomainVO.java +++ b/engine/schema/src/main/java/com/cloud/domain/DomainVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.logging.log4j.Logger; diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java index 5b4e4c591ffc..e8b702d2cb9b 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDetailsDaoImpl.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.ConfigKey.Scope; import org.apache.cloudstack.framework.config.ScopedConfigStorage; diff --git a/engine/schema/src/main/java/com/cloud/event/EventVO.java b/engine/schema/src/main/java/com/cloud/event/EventVO.java index 24c3e8cd0641..28a5a4fab848 100644 --- a/engine/schema/src/main/java/com/cloud/event/EventVO.java +++ b/engine/schema/src/main/java/com/cloud/event/EventVO.java @@ -19,15 +19,15 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/event/UsageEventDetailsVO.java b/engine/schema/src/main/java/com/cloud/event/UsageEventDetailsVO.java index 98f7d610f671..e3a56e985c8a 100644 --- a/engine/schema/src/main/java/com/cloud/event/UsageEventDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/event/UsageEventDetailsVO.java @@ -16,10 +16,10 @@ // under the License. package com.cloud.event; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "usage_event_details") diff --git a/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java b/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java index 41ecec0c7fb8..93cc8776c35c 100644 --- a/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java +++ b/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java b/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java index bce9c474e2d2..d77958b33b4c 100644 --- a/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.TimeZone; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java b/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java index 2410077c84ad..56e14f470323 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java @@ -21,12 +21,12 @@ import org.apache.cloudstack.gpu.GpuCard; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java b/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java index ac20e74c3604..f47d5af2eae7 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java @@ -19,14 +19,14 @@ import org.apache.cloudstack.gpu.GpuDevice; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.UUID; @Entity diff --git a/engine/schema/src/main/java/com/cloud/gpu/HostGpuGroupsVO.java b/engine/schema/src/main/java/com/cloud/gpu/HostGpuGroupsVO.java index f8e116cbc99c..a84463e480cf 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/HostGpuGroupsVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/HostGpuGroupsVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.gpu; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java b/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java index 4944d51f1b44..5fc0615533bf 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.gpu; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java b/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java index 86f5eb94415b..184b8058700d 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java @@ -21,12 +21,12 @@ import org.apache.cloudstack.gpu.VgpuProfile; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java index 8aad85d45086..9f0b6d53de08 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java @@ -24,7 +24,7 @@ import com.cloud.utils.db.SearchCriteria; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; @Component diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java index bd7032aff27b..88adecaa740e 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java @@ -29,7 +29,7 @@ import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import java.util.Collections; import java.util.List; diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java index 524feed24679..6ea23a7a257e 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java @@ -26,7 +26,7 @@ import com.cloud.utils.exception.CloudRuntimeException; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java index 11dd7edb30d5..b23fdcce8e28 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java @@ -24,7 +24,7 @@ import com.cloud.utils.db.SearchCriteria; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; @Component diff --git a/engine/schema/src/main/java/com/cloud/host/DetailVO.java b/engine/schema/src/main/java/com/cloud/host/DetailVO.java index 1c781c68407a..76d926438b9e 100644 --- a/engine/schema/src/main/java/com/cloud/host/DetailVO.java +++ b/engine/schema/src/main/java/com/cloud/host/DetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.host; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/host/HostTagVO.java b/engine/schema/src/main/java/com/cloud/host/HostTagVO.java index 98071a2c0732..fba967645f77 100644 --- a/engine/schema/src/main/java/com/cloud/host/HostTagVO.java +++ b/engine/schema/src/main/java/com/cloud/host/HostTagVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.host; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; import org.apache.commons.lang3.BooleanUtils; diff --git a/engine/schema/src/main/java/com/cloud/host/HostVO.java b/engine/schema/src/main/java/com/cloud/host/HostVO.java index d51b4eca0577..c2ba71ee0626 100644 --- a/engine/schema/src/main/java/com/cloud/host/HostVO.java +++ b/engine/schema/src/main/java/com/cloud/host/HostVO.java @@ -25,22 +25,22 @@ import java.util.Set; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.cpu.CPU; import org.apache.cloudstack.util.CPUArchConverter; diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java index 99c9a979c3bf..ae56b7001344 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDaoImpl.java @@ -31,9 +31,9 @@ import java.util.TimeZone; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.persistence.TableGenerator; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.persistence.TableGenerator; import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java index d3fee6a26761..f9e90e155bed 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java @@ -35,7 +35,7 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.SearchCriteria.Func; -import javax.inject.Inject; +import jakarta.inject.Inject; @Component public class HostTagsDaoImpl extends GenericDaoBase implements HostTagsDao, Configurable { diff --git a/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java b/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java index a3b03280fdf6..20d83a15f56b 100644 --- a/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java +++ b/engine/schema/src/main/java/com/cloud/hypervisor/HypervisorCapabilitiesVO.java @@ -18,13 +18,13 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.utils.NumbersUtil; diff --git a/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java b/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java index 769965dfdf11..0fa2183613b0 100644 --- a/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/Ipv6GuestPrefixSubnetNetworkMapVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java b/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java index ee5f67b09cd1..4cc231bda9ac 100644 --- a/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java @@ -18,13 +18,13 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import com.cloud.network.rules.HealthCheckPolicy; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/network/TungstenGuestNetworkIpAddressVO.java b/engine/schema/src/main/java/com/cloud/network/TungstenGuestNetworkIpAddressVO.java index 6ff92ac63984..c9017016e28f 100644 --- a/engine/schema/src/main/java/com/cloud/network/TungstenGuestNetworkIpAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/TungstenGuestNetworkIpAddressVO.java @@ -19,14 +19,14 @@ import com.cloud.utils.net.Ip; import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = ("tungsten_guest_network_ip_address")) diff --git a/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java b/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java index 65688179602a..9e9837a4560a 100644 --- a/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/UserIpv6AddressVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java b/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java index aadbd3f375ee..0c6946e26cb5 100644 --- a/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java +++ b/engine/schema/src/main/java/com/cloud/network/VpnUserVO.java @@ -18,14 +18,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.Encrypt; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyConditionMapVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyConditionMapVO.java index 6a3118bd5b3a..437f66bd1fbc 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyConditionMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyConditionMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.as; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java index 24d8b8e7f40a..72c2e6162127 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScalePolicyVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupPolicyMapVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupPolicyMapVO.java index 403d2c0a2b1e..a87745b0cef6 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupPolicyMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupPolicyMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.as; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupStatisticsVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupStatisticsVO.java index 8ca88abc226e..41239b44dbe1 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupStatisticsVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupStatisticsVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.network.router.VirtualRouterAutoScale; import com.cloud.server.ResourceTag; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java index 307de9f1a60d..46999e7a5838 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVmMapVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVmMapVO.java index 0d9139b799fc..525c020a31c5 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVmMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmGroupVmMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.as; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java index 562d908507e8..fb5e8b31c7b9 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/AutoScaleVmProfileVO.java @@ -24,16 +24,16 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Basic; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java b/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java index 0679dac32355..0da09e89d278 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/ConditionVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/CounterVO.java b/engine/schema/src/main/java/com/cloud/network/as/CounterVO.java index be21515bb51a..6c313a300468 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/CounterVO.java +++ b/engine/schema/src/main/java/com/cloud/network/as/CounterVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImpl.java index 18c1b7f3f3ce..13c0bdfd977b 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupDaoImpl.java @@ -28,7 +28,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Component public class AutoScaleVmGroupDaoImpl extends GenericDaoBase implements AutoScaleVmGroupDao { diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImpl.java index eee1b6c11913..2dab2d6364ad 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupStatisticsDaoImpl.java @@ -28,7 +28,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Component public class AutoScaleVmGroupStatisticsDaoImpl extends GenericDaoBase implements AutoScaleVmGroupStatisticsDao { diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java index b2f4e578a82f..ad571ebf51ce 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/AutoScaleVmGroupVmMapDaoImpl.java @@ -18,8 +18,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapVO.java index 2826abf9cacc..6ff200a42402 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/AccountGuestVlanMapVO.java @@ -18,12 +18,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.GuestVlanRange; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceVO.java b/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceVO.java index 141eb530fd07..9452b73dd291 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/ExternalFirewallDeviceVO.java @@ -18,14 +18,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java b/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java index 88c5c0885a87..fdb7e067de7a 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/ExternalLoadBalancerDeviceVO.java @@ -18,14 +18,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsVO.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsVO.java index bad7479cde70..16819b8b6703 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesCidrsVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java index 27bf7ba6aa83..f268006364c5 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDestCidrsVO.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDestCidrsVO.java index 58a19eb4557f..22645d38c61d 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDestCidrsVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDestCidrsVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "firewall_rules_dcidrs") diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java index 0a5ecd25667e..db3103e6d6d8 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java @@ -21,8 +21,8 @@ import java.util.Date; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.resourcedetail.dao.UserIpAddressDetailsDao; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java index a3a65fdb01b3..c3c1dc0e7a05 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.network.IpAddress; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapVO.java index 923a26164084..86842ec69658 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/InlineLoadBalancerNicMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java index fac6d4825fa3..0ab42ed8e51c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Ipv6GuestPrefixSubnetNetworkMapDaoImpl.java @@ -19,7 +19,7 @@ import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java index 72b8fc151b78..ab91b8094bc4 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java @@ -23,13 +23,13 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import com.cloud.network.rules.StickinessPolicy; import com.cloud.utils.Pair; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapDaoImpl.java index 2833c332b025..a9a7cd051324 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.JoinBuilder; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java index f95c61733f28..5b5a3f82953d 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java @@ -18,10 +18,10 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerDaoImpl.java index 95d6a1b7125a..8f60271636e0 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerDaoImpl.java @@ -18,8 +18,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import com.cloud.network.rules.FirewallRule; import com.cloud.utils.db.JoinBuilder; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java index 721349613d88..3dc582133b99 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java index 3886529322e3..022c267003e0 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import com.cloud.network.rules.FirewallRuleVO; import com.cloud.network.rules.LoadBalancer; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java b/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java index 97e8a041c6cc..d4c199864738 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/MonitoringServiceVO.java @@ -18,12 +18,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.MonitoringService; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountVO.java index 208d13075d50..62c86a8f6319 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkAccountVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java index 9f7ffabac930..28671ef29c3a 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java @@ -25,9 +25,9 @@ import java.util.Random; import java.util.Set; -import javax.annotation.PostConstruct; -import javax.inject.Inject; -import javax.persistence.TableGenerator; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; +import jakarta.persistence.TableGenerator; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.acl.ControlledEntity.ACLType; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailVO.java index 23c85f7f0444..674f58b31716 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainVO.java index 99cbb50953a8..cd2bb08f9c01 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDomainVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallVO.java index 9cf9a6158da0..0ac1e55d7f94 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalFirewallVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerVO.java index 6ba5c8fd8b86..8b1f1e54de85 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkExternalLoadBalancerVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpVO.java index ea381166b07c..538a1926f648 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkOpVO.java @@ -16,10 +16,10 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigVO.java index 7c2467f8d573..e75b7c87bfae 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkRuleConfigVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java index 4e1e9aff4561..f89a68082ad8 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java index f2572ba91c21..7bc11f8c8a29 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Transient; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.commons.lang3.StringUtils; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceVO.java b/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceVO.java index c2882fc4eaf7..1dbfdc0398bc 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/OpRouterMonitorServiceVO.java @@ -19,10 +19,10 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Entity; -import javax.persistence.Table; -import javax.persistence.Id; -import javax.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Id; +import jakarta.persistence.Column; @Entity diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkDaoImpl.java index 235db0cec442..d8b4d88bd67b 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkIsolationMethodVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkIsolationMethodVO.java index 65d776ed5b85..fabc3f9943fb 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkIsolationMethodVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkIsolationMethodVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java index 9557c7465bff..b33ca47a7263 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java @@ -21,14 +21,14 @@ import java.util.List; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTagVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTagVO.java index df9030aacf6d..cf3e996906fd 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTagVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTagVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java index fdd827ffeeef..15b612abad62 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java @@ -39,9 +39,7 @@ public class PhysicalNetworkTrafficTypeDaoImpl extends GenericDaoBase xenAllFieldsSearch; final GenericSearchBuilder vmWareAllFieldsSearch; final GenericSearchBuilder simulatorAllFieldsSearch; - final GenericSearchBuilder ovmAllFieldsSearch; final GenericSearchBuilder hypervAllFieldsSearch; - final GenericSearchBuilder ovm3AllFieldsSearch; protected PhysicalNetworkTrafficTypeDaoImpl() { super(); @@ -80,17 +78,6 @@ protected PhysicalNetworkTrafficTypeDaoImpl() { simulatorAllFieldsSearch.selectFields(simulatorAllFieldsSearch.entity().getSimulatorNetworkLabel()); simulatorAllFieldsSearch.done(); - ovmAllFieldsSearch = createSearchBuilder(String.class); - ovmAllFieldsSearch.and("physicalNetworkId", ovmAllFieldsSearch.entity().getPhysicalNetworkId(), Op.EQ); - ovmAllFieldsSearch.and("trafficType", ovmAllFieldsSearch.entity().getTrafficType(), Op.EQ); - ovmAllFieldsSearch.selectFields(ovmAllFieldsSearch.entity().getSimulatorNetworkLabel()); - ovmAllFieldsSearch.done(); - - ovm3AllFieldsSearch = createSearchBuilder(String.class); - ovm3AllFieldsSearch.and("physicalNetworkId", ovm3AllFieldsSearch.entity().getPhysicalNetworkId(), Op.EQ); - ovm3AllFieldsSearch.and("trafficType", ovm3AllFieldsSearch.entity().getTrafficType(), Op.EQ); - ovm3AllFieldsSearch.selectFields(ovm3AllFieldsSearch.entity().getOvm3NetworkLabel()); - ovm3AllFieldsSearch.done(); } @Override @@ -123,14 +110,10 @@ public String getNetworkTag(long physicalNetworkId, TrafficType trafficType, Hyp sc = vmWareAllFieldsSearch.create(); } else if (hType == HypervisorType.Simulator) { sc = simulatorAllFieldsSearch.create(); - } else if (hType == HypervisorType.Ovm) { - sc = ovmAllFieldsSearch.create(); } else if (hType == HypervisorType.BareMetal || hType == HypervisorType.External) { return null; } else if (hType == HypervisorType.Hyperv) { sc = hypervAllFieldsSearch.create(); - } else if (hType == HypervisorType.Ovm3) { - sc = ovm3AllFieldsSearch.create(); } else { assert (false) : "We don't handle this hypervisor type"; return null; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeVO.java index a5eff2aeab90..3c60b6ded12e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeVO.java @@ -18,14 +18,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.Networks.TrafficType; import com.cloud.network.PhysicalNetworkTrafficType; @@ -63,6 +63,7 @@ public class PhysicalNetworkTrafficTypeVO implements PhysicalNetworkTrafficType @Column(name = "hyperv_network_label") private String hypervNetworkLabel; + /** @deprecated OVM3 hypervisor plugin has been removed. Column retained for DB compatibility. */ @Column(name = "ovm_network_label") private String ovm3NetworkLabel; @@ -164,12 +165,4 @@ public String getHypervNetworkLabel() { return hypervNetworkLabel; } - public void setOvm3NetworkLabel(String ovm3NetworkLabel) { - this.ovm3NetworkLabel = ovm3NetworkLabel; - } - - @Override - public String getOvm3NetworkLabel() { - return ovm3NetworkLabel; - } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkVO.java index 68e023984a0c..96a6d552a0ad 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkVO.java @@ -21,17 +21,17 @@ import java.util.List; import java.util.UUID; -import javax.persistence.CollectionTable; -import javax.persistence.Column; -import javax.persistence.ElementCollection; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.Table; -import javax.persistence.TableGenerator; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; import com.cloud.network.PhysicalNetwork; import com.cloud.utils.NumbersUtil; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PortProfileVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PortProfileVO.java index 94ea0891057a..649d1fa20697 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PortProfileVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PortProfileVO.java @@ -19,12 +19,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java index a1b789b8a46b..0e0016f8df4e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDaoImpl.java @@ -24,8 +24,8 @@ import com.cloud.utils.db.SearchCriteria; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; @Component public class PublicIpQuarantineDaoImpl extends GenericDaoBase implements PublicIpQuarantineDao { diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java index 2439ea55b4a8..20463127f392 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnVO.java @@ -20,12 +20,12 @@ import com.cloud.utils.db.Encrypt; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.UUID; @Entity diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java index 204ef2d15381..bbe0b1ad68a6 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java @@ -19,14 +19,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.network.RouterHealthCheckResult; import com.cloud.network.VirtualNetworkApplianceService; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkVO.java index 5808af3a3e82..636cd311167a 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RouterNetworkVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.network.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayVO.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayVO.java index e5394238c315..41babdd54630 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteCustomerGatewayVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.Site2SiteCustomerGateway; import com.cloud.utils.db.Encrypt; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionDaoImpl.java index f9c5ce089645..aafa5a8ba9be 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionDaoImpl.java @@ -18,8 +18,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java index 4d6bee5c8614..f50b1dc3815b 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnConnectionVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java index 0aeefe90c29e..da0cb4e78fb3 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java @@ -16,7 +16,7 @@ // under the License. package com.cloud.network.dao; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java index a5eb7efce234..98dec766a5f3 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.Site2SiteVpnGateway; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/dao/SslCertVO.java b/engine/schema/src/main/java/com/cloud/network/dao/SslCertVO.java index c33551ea5c7a..2d1583a44b65 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/SslCertVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/SslCertVO.java @@ -18,10 +18,10 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.network.tls.SslCert; import com.cloud.utils.db.Encrypt; diff --git a/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java index 113678f7b010..fdc668618a31 100644 --- a/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java @@ -18,12 +18,12 @@ import com.cloud.network.netris.NetrisProvider; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/network/element/NsxProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/NsxProviderVO.java index f08e08b1ca04..bdb721f23d6f 100644 --- a/engine/schema/src/main/java/com/cloud/network/element/NsxProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/element/NsxProviderVO.java @@ -19,12 +19,12 @@ import com.cloud.network.nsx.NsxProvider; import com.cloud.utils.db.Encrypt; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/network/element/OvsProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/OvsProviderVO.java index db82845dd4bb..f025f9c59d60 100644 --- a/engine/schema/src/main/java/com/cloud/network/element/OvsProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/element/OvsProviderVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.OvsProvider; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/element/TungstenProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/TungstenProviderVO.java index 7529e129ce62..9ee0c357b1e5 100644 --- a/engine/schema/src/main/java/com/cloud/network/element/TungstenProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/element/TungstenProviderVO.java @@ -18,12 +18,12 @@ import com.cloud.network.TungstenProvider; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.UUID; @Entity diff --git a/engine/schema/src/main/java/com/cloud/network/element/VirtualRouterProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/VirtualRouterProviderVO.java index 08dc1a95f432..abb9c4e464e8 100644 --- a/engine/schema/src/main/java/com/cloud/network/element/VirtualRouterProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/element/VirtualRouterProviderVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.VirtualRouterProvider; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java index 1dfdc5093a59..1a713a3e8b0f 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java @@ -20,19 +20,19 @@ import java.util.List; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import com.cloud.utils.db.GenericDao; import com.cloud.utils.net.NetUtils; diff --git a/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java index 576e2f8172e6..24e122c31332 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/PortForwardingRuleVO.java @@ -18,14 +18,14 @@ import java.util.List; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import com.cloud.utils.net.Ip; diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java index 637f47731b47..2c76a3faf49a 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRuleVO.java b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRuleVO.java index 325a6efc867f..9e91aef426b8 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRuleVO.java @@ -20,12 +20,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "security_group_rule") diff --git a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRulesVO.java b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRulesVO.java index 0bfc8a68bddc..3eaaee92aeed 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRulesVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupRulesVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.network.security; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.Table; import com.cloud.network.security.SecurityRule.SecurityRuleType; import com.cloud.utils.db.JoinType; diff --git a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVMMapVO.java b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVMMapVO.java index 59699cba1d40..a4ba52a11f0a 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVMMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVMMapVO.java @@ -16,15 +16,15 @@ // under the License. package com.cloud.network.security; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.SecondaryTables; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.SecondaryTables; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVO.java b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVO.java index 940baaad18d7..bf07aca00ac9 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupVO.java @@ -20,12 +20,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "security_group") diff --git a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupWorkVO.java b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupWorkVO.java index f646b6fdb34e..c2d5d8c73b10 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupWorkVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/SecurityGroupWorkVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/security/TungstenSecurityGroupRuleVO.java b/engine/schema/src/main/java/com/cloud/network/security/TungstenSecurityGroupRuleVO.java index dc438db712e4..d000c047fa69 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/TungstenSecurityGroupRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/TungstenSecurityGroupRuleVO.java @@ -18,12 +18,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = ("tungsten_security_group_rule")) diff --git a/engine/schema/src/main/java/com/cloud/network/security/VmRulesetLogVO.java b/engine/schema/src/main/java/com/cloud/network/security/VmRulesetLogVO.java index 5d2c948ec432..94d51476e749 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/VmRulesetLogVO.java +++ b/engine/schema/src/main/java/com/cloud/network/security/VmRulesetLogVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java index 019cf5fec462..41a1779640f0 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRuleDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRuleDaoImpl.java index 64c42bf7eca0..cf8b57d95e4c 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRuleDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupRuleDaoImpl.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.server.ResourceTag.ResourceObjectType; diff --git a/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java b/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java index 89e02610bd20..f685a35f4653 100644 --- a/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vo/PublicIpQuarantineVO.java @@ -19,14 +19,14 @@ import com.cloud.network.PublicIpQuarantine; import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemCidrsVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemCidrsVO.java index c366f947961a..bc1dc9e9d056 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemCidrsVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemCidrsVO.java @@ -18,12 +18,12 @@ */ package com.cloud.network.vpc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemVO.java index 4333d35d4733..e69b413319c8 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLItemVO.java @@ -22,15 +22,15 @@ import java.util.StringTokenizer; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import com.cloud.utils.db.GenericDao; import com.cloud.utils.exception.CloudRuntimeException; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java index 37b9e7ff296a..81e3593c8458 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/NetworkACLVO.java @@ -21,12 +21,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "network_acl") diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/PrivateIpVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/PrivateIpVO.java index d03e55d6b28d..ae61b8e942b7 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/PrivateIpVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/PrivateIpVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java index 632d96819cd3..283111b892f6 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java @@ -19,15 +19,15 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java index b1d4df35d4ca..0afde36ece40 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcGatewayVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingDetailsVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingDetailsVO.java index 3197ffc10ca8..ba1c2cecb754 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingDetailsVO.java @@ -17,12 +17,12 @@ package com.cloud.network.vpc; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingServiceMapVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingServiceMapVO.java index c365ae7b441b..afc206125f74 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingServiceMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingServiceMapVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java index b913468384e4..4e5f3183e8a2 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.offering.NetworkOffering; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java index 9fa4b505c650..d44230910ae9 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java index 742d3f2f82ee..8cda9e0cdbde 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcVO.java @@ -19,13 +19,13 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLItemDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLItemDaoImpl.java index 925515f6f4ab..5890f14c7b24 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLItemDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/NetworkACLItemDaoImpl.java @@ -20,7 +20,7 @@ import java.sql.SQLException; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/StaticRouteDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/StaticRouteDaoImpl.java index 671bf4507580..4847b79f37be 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/StaticRouteDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/StaticRouteDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java index 4e13fe4f5d0d..db6d3104784e 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java index b83fd8913059..62ea30fe9b4a 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingDaoImpl.java @@ -17,7 +17,7 @@ package com.cloud.network.vpc.dao; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.offering.NetworkOffering; import org.apache.cloudstack.api.ApiConstants; diff --git a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java index 545371afeff6..703083549860 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingDetailsVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.offerings; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingServiceMapVO.java b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingServiceMapVO.java index 5171b1b8a87e..b3b35293dd1d 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingServiceMapVO.java +++ b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingServiceMapVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java index 904c8e646eb5..731d636f26bc 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.Network; import com.cloud.network.Networks.TrafficType; diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java index 9bc74b139320..3362e12a193f 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingDaoImpl.java @@ -20,8 +20,8 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; -import javax.persistence.EntityExistsException; +import jakarta.inject.Inject; +import jakarta.persistence.EntityExistsException; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java index 4710a815f978..fac0239f9052 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectAccountVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java index 887939311b24..b7d9c61247e2 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectInvitationVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/projects/ProjectVO.java b/engine/schema/src/main/java/com/cloud/projects/ProjectVO.java index 4ac34eeab4c2..a68d3f077c7a 100644 --- a/engine/schema/src/main/java/com/cloud/projects/ProjectVO.java +++ b/engine/schema/src/main/java/com/cloud/projects/ProjectVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java index 46bf36ae397b..3cb38cbd482e 100644 --- a/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/projects/dao/ProjectDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java b/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java index fb9e3935d0e2..81dd5c7b3cb6 100644 --- a/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java +++ b/engine/schema/src/main/java/com/cloud/resource/icon/ResourceIconVO.java @@ -19,16 +19,16 @@ import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceTag; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.GenerationType; -import javax.persistence.Column; -import javax.persistence.Enumerated; -import javax.persistence.EnumType; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Column; +import jakarta.persistence.Enumerated; +import jakarta.persistence.EnumType; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogVO.java b/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogVO.java index a3886bc611d0..8b1b8e438e9e 100644 --- a/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogVO.java +++ b/engine/schema/src/main/java/com/cloud/secstorage/CommandExecLogVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingDetailsVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingDetailsVO.java index 5aa603e1c3e0..f16fbe73fbfd 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingDetailsVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.service; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index cfe8049f5b2c..20b2f29b5434 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -20,17 +20,17 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java index f360770ad686..fcb57c2d6322 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -21,8 +21,8 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; -import javax.persistence.EntityExistsException; +import jakarta.inject.Inject; +import jakarta.persistence.EntityExistsException; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.dao.DiskOfferingDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/BucketVO.java b/engine/schema/src/main/java/com/cloud/storage/BucketVO.java index a54c1dd9b081..ffbd32a7924b 100644 --- a/engine/schema/src/main/java/com/cloud/storage/BucketVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/BucketVO.java @@ -21,14 +21,14 @@ import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java index 7f6b6d8adf0e..b942ba3e7dd5 100644 --- a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java @@ -20,17 +20,17 @@ import java.util.List; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.offering.DiskOffering; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java b/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java index 642705ffcbe4..a37ba7158f42 100644 --- a/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java b/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java index cae1e1b7eeed..eb58542cdce1 100644 --- a/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSHypervisorVO.java @@ -19,13 +19,13 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/GuestOSVO.java b/engine/schema/src/main/java/com/cloud/storage/GuestOSVO.java index 03955f17667d..31a43b9612b2 100644 --- a/engine/schema/src/main/java/com/cloud/storage/GuestOSVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/LaunchPermissionVO.java b/engine/schema/src/main/java/com/cloud/storage/LaunchPermissionVO.java index cc4f3fce407c..a00a2d227da8 100644 --- a/engine/schema/src/main/java/com/cloud/storage/LaunchPermissionVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/LaunchPermissionVO.java @@ -16,10 +16,10 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java index 299c6380ab63..cdd68a97a25b 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java @@ -18,12 +18,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.storage.snapshot.SnapshotPolicy; import com.cloud.utils.DateUtil.IntervalType; diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java index 5e013e76d3c8..4eb8e1a6926d 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotScheduleVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.storage.snapshot.SnapshotSchedule; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java index 4a504333344f..dbb19fe83140 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java @@ -19,15 +19,15 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.util.HypervisorTypeConverter; diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java index 82860defd6de..ae2d188e7ba0 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotZoneVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java index 5690324340c4..718887da1080 100644 --- a/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java index 73a11b02c050..506570508b9e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.db.GenericDaoBase; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java index 2675c36f27f0..c6fb8ea7f225 100755 --- a/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolTagVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.NumbersUtil; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolWorkVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolWorkVO.java index 53155bff31dc..b302292c1b08 100644 --- a/engine/schema/src/main/java/com/cloud/storage/StoragePoolWorkVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolWorkVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/storage/UploadVO.java b/engine/schema/src/main/java/com/cloud/storage/UploadVO.java index dda5c38b5961..4296fb4f2765 100644 --- a/engine/schema/src/main/java/com/cloud/storage/UploadVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/UploadVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.NumbersUtil; import com.cloud.utils.db.GenericDaoBase; diff --git a/engine/schema/src/main/java/com/cloud/storage/VMTemplateDetailVO.java b/engine/schema/src/main/java/com/cloud/storage/VMTemplateDetailVO.java index 5010edfa762c..139dc52d4b0e 100755 --- a/engine/schema/src/main/java/com/cloud/storage/VMTemplateDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VMTemplateDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/storage/VMTemplateStoragePoolVO.java b/engine/schema/src/main/java/com/cloud/storage/VMTemplateStoragePoolVO.java index 69c9c85ab5aa..c832894fffe2 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VMTemplateStoragePoolVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VMTemplateStoragePoolVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; diff --git a/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java b/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java index 88d3b7ba2d8d..6b57317a43d1 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java @@ -20,17 +20,17 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.cpu.CPU; import com.cloud.user.UserData; diff --git a/engine/schema/src/main/java/com/cloud/storage/VMTemplateZoneVO.java b/engine/schema/src/main/java/com/cloud/storage/VMTemplateZoneVO.java index 2f4c882a5932..c7464f455d06 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VMTemplateZoneVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VMTemplateZoneVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java index 24d8191fa045..0ad45328b340 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateDetailVO.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Lob; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java index 1f5054c0cd83..9060e37fd303 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VnfTemplateNicVO.java @@ -19,12 +19,12 @@ import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "vnf_template_nics") diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java index 42980e07b2bb..70c1b6e884f3 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VolumeDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.storage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java index 2d1817421230..85088759a2fc 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VolumeStatsVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java index 653be54a9109..22542cc53cab 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java @@ -19,19 +19,19 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.util.StoragePoolTypeConverter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java index 4ca3fe9f12ac..befef5251f82 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java @@ -23,8 +23,8 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; -import javax.persistence.EntityExistsException; +import jakarta.inject.Inject; +import jakarta.persistence.EntityExistsException; import com.cloud.offering.DiskOffering; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java index f167b5731878..a48f46bb5a4c 100755 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java @@ -22,8 +22,8 @@ import java.util.Arrays; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsVO.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsVO.java index f217501a3021..f9b21ccf3d3c 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDetailsVO.java @@ -18,12 +18,12 @@ */ package com.cloud.storage.dao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java index a3baa3b4cb06..85d2b657daef 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java @@ -19,7 +19,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java index 55b5668bbc17..ec75df2ed25f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java @@ -23,8 +23,8 @@ import java.util.List; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java index c4d7ed886072..469c549b6abf 100755 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolTagsDaoImpl.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.response.StorageTagResponse; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java index 9b5d0edc599d..ce7f82fb48e4 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java @@ -26,7 +26,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java index 5dfc138d8e1b..fff6cc72887c 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplatePoolDaoImpl.java @@ -24,7 +24,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index fce4d1f7233d..b6a617457727 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.reservation.ReservationVO; import org.apache.cloudstack.reservation.dao.ReservationDao; diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java index e4c19fd1666b..138f853bf8de 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeStatsDaoImpl.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java b/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java index 1db9a618bbf5..213b5c1a3be5 100644 --- a/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java +++ b/engine/schema/src/main/java/com/cloud/tags/ResourceTagVO.java @@ -18,14 +18,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.server.ResourceTag; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java b/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java index 5c1a75046927..e21c9a9017bb 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDaoImpl; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java index cccfbe8a0065..5b2bfa1e2d1e 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseCreator.java @@ -215,7 +215,7 @@ public static void main(String[] args) { System.err.println("The class must be of SystemIntegrityChecker: " + clazz.getName()); System.exit(1); } - SystemIntegrityChecker checker = (SystemIntegrityChecker)clazz.newInstance(); + SystemIntegrityChecker checker = (SystemIntegrityChecker)clazz.getDeclaredConstructor().newInstance(); checker.check(); } catch (ClassNotFoundException e) { System.err.println("Unable to find " + upgradeClass + ": " + e.getMessage()); @@ -226,6 +226,9 @@ public static void main(String[] args) { } catch (IllegalAccessException e) { System.err.println("Unable to access " + upgradeClass + ": " + e.getMessage()); System.exit(1); + } catch (ReflectiveOperationException e) { + System.err.println("Unable to instantiate " + upgradeClass + ": " + e.getMessage()); + System.exit(1); } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java index e7ea6025ad76..0c81a6496754 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseIntegrityChecker.java @@ -21,7 +21,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java index 0e784d961b3d..334cb4c480f2 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java @@ -31,7 +31,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.utils.CloudStackVersion; import org.apache.commons.lang3.StringUtils; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java b/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java index abb0d7f76690..327ebf437685 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/GuestOsMapper.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.storage.GuestOSHypervisorMapping; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java index 2acb4138d234..a03b39ca92b2 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java @@ -36,7 +36,7 @@ import java.util.UUID; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -310,8 +310,7 @@ public void setUpdated(Date updated) { new Pair<>(Hypervisor.HypervisorType.VMware, CPU.CPUArch.amd64), new Pair<>(Hypervisor.HypervisorType.XenServer, CPU.CPUArch.amd64), new Pair<>(Hypervisor.HypervisorType.Hyperv, CPU.CPUArch.amd64), - new Pair<>(Hypervisor.HypervisorType.LXC, CPU.CPUArch.amd64), - new Pair<>(Hypervisor.HypervisorType.Ovm3, CPU.CPUArch.amd64) + new Pair<>(Hypervisor.HypervisorType.LXC, CPU.CPUArch.amd64) ); protected static final List METADATA_TEMPLATE_LIST = new ArrayList<>(); @@ -323,7 +322,6 @@ public void setUpdated(Date updated) { put(Hypervisor.HypervisorType.XenServer, "router.template.xenserver"); put(Hypervisor.HypervisorType.Hyperv, "router.template.hyperv"); put(Hypervisor.HypervisorType.LXC, "router.template.lxc"); - put(Hypervisor.HypervisorType.Ovm3, "router.template.ovm3"); } }; @@ -334,7 +332,6 @@ public void setUpdated(Date updated) { put(Hypervisor.HypervisorType.VMware, ImageFormat.OVA); put(Hypervisor.HypervisorType.Hyperv, ImageFormat.VHD); put(Hypervisor.HypervisorType.LXC, ImageFormat.QCOW2); - put(Hypervisor.HypervisorType.Ovm3, ImageFormat.RAW); } }; @@ -345,7 +342,6 @@ public void setUpdated(Date updated) { put(Hypervisor.HypervisorType.VMware, OTHER_LINUX_ID); put(Hypervisor.HypervisorType.Hyperv, LINUX_12_ID); put(Hypervisor.HypervisorType.LXC, LINUX_12_ID); - put(Hypervisor.HypervisorType.Ovm3, LINUX_12_ID); } }; @@ -709,7 +705,6 @@ protected void updateHypervisorGuestOsMap() { hypervisorGuestOsMap.put(Hypervisor.HypervisorType.KVM, LINUX_12_ID); hypervisorGuestOsMap.put(Hypervisor.HypervisorType.Hyperv, LINUX_12_ID); hypervisorGuestOsMap.put(Hypervisor.HypervisorType.LXC, LINUX_12_ID); - hypervisorGuestOsMap.put(Hypervisor.HypervisorType.Ovm3, LINUX_12_ID); } catch (Exception e) { LOGGER.warn("Couldn't update System VM template guest OS ID, due to {}", e.getMessage()); } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41500to41510.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41500to41510.java index c7295414326d..2000199a6c20 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41500to41510.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41500to41510.java @@ -34,7 +34,6 @@ import static com.cloud.hypervisor.Hypervisor.HypervisorType.Hyperv; import static com.cloud.hypervisor.Hypervisor.HypervisorType.KVM; import static com.cloud.hypervisor.Hypervisor.HypervisorType.LXC; -import static com.cloud.hypervisor.Hypervisor.HypervisorType.Ovm3; import static com.cloud.hypervisor.Hypervisor.HypervisorType.VMware; import static com.cloud.hypervisor.Hypervisor.HypervisorType.XenServer; @@ -89,8 +88,6 @@ public void updateSystemVmTemplates(final Connection conn) { hypervisorsListInUse.add(Hyperv); } else if (type.equals(LXC)) { hypervisorsListInUse.add(LXC); - } else if (type.equals(Ovm3)) { - hypervisorsListInUse.add(Ovm3); } } } catch (final SQLException e) { @@ -105,7 +102,6 @@ public void updateSystemVmTemplates(final Connection conn) { put(XenServer, "systemvm-xenserver-4.15.1"); put(Hyperv, "systemvm-hyperv-4.15.1"); put(LXC, "systemvm-lxc-4.15.1"); - put(Ovm3, "systemvm-ovm3-4.15.1"); } }; @@ -116,7 +112,6 @@ public void updateSystemVmTemplates(final Connection conn) { put(XenServer, "router.template.xenserver"); put(Hyperv, "router.template.hyperv"); put(LXC, "router.template.lxc"); - put(Ovm3, "router.template.ovm3"); } }; @@ -127,7 +122,6 @@ public void updateSystemVmTemplates(final Connection conn) { put(XenServer, "https://download.cloudstack.org/systemvm/4.15/systemvmtemplate-4.15.1-xen.vhd.bz2"); put(Hyperv, "https://download.cloudstack.org/systemvm/4.15/systemvmtemplate-4.15.1-hyperv.vhd.zip"); put(LXC, "https://download.cloudstack.org/systemvm/4.15/systemvmtemplate-4.15.1-kvm.qcow2.bz2"); - put(Ovm3, "https://download.cloudstack.org/systemvm/4.15/systemvmtemplate-4.15.1-ovm.raw.bz2"); } }; @@ -138,7 +132,6 @@ public void updateSystemVmTemplates(final Connection conn) { put(VMware, "4006982765846d373eb3719b2fe4d720"); put(Hyperv, "0b9514e4b6cba1f636fea2125f0f7a5f"); put(LXC, "0e9f9a7d0957c3e0a2088e41b2da2cec"); - put(Ovm3, "ae3977e696b3e6c81bdcbb792d514d29"); } }; diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionVO.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionVO.java index 48e4766e7157..5ddc277cab9a 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionVO.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/VersionVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.java b/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.java index ab5fcfc493cf..cecda091f999 100644 --- a/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/BucketStatisticsVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "bucket_statistics") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java index 43e3974aa0e3..2d36359a0fa5 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageBackupVO.java @@ -19,14 +19,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageIPAddressVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageIPAddressVO.java index 330ab6557817..717f700b9177 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageIPAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageIPAddressVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageJobVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageJobVO.java index d00e67a1069f..cd9a08c3be8d 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageJobVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageJobVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageLoadBalancerPolicyVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageLoadBalancerPolicyVO.java index e8bbb91f52d8..ed5f52c17726 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageLoadBalancerPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageLoadBalancerPolicyVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageNetworkOfferingVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageNetworkOfferingVO.java index f16f1ea5ac1c..83465699274e 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageNetworkOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageNetworkOfferingVO.java @@ -20,14 +20,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "usage_network_offering") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageNetworkVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageNetworkVO.java index 10b86542893c..0345cdb38f8f 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageNetworkVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageNetworkVO.java @@ -16,10 +16,10 @@ // under the License. package com.cloud.usage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "usage_network") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageNetworksVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageNetworksVO.java index 1385e69da038..9fa81f1f2c16 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageNetworksVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageNetworksVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.usage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; import java.util.Date; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsagePortForwardingRuleVO.java b/engine/schema/src/main/java/com/cloud/usage/UsagePortForwardingRuleVO.java index 2ee10c7261e4..b31ac992c515 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsagePortForwardingRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsagePortForwardingRuleVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageSecurityGroupVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageSecurityGroupVO.java index 7ca9c715b74b..a4d0076fd6e6 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageSecurityGroupVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageSecurityGroupVO.java @@ -20,14 +20,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "usage_security_group") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageSnapshotOnPrimaryVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageSnapshotOnPrimaryVO.java index db912c33a8ef..86c645e5791a 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageSnapshotOnPrimaryVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageSnapshotOnPrimaryVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageStorageVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageStorageVO.java index 08c192d594a3..a87e4cfce470 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageStorageVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageStorageVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVMInstanceVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVMInstanceVO.java index 38c89826849e..417cb09c79d2 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVMInstanceVO.java @@ -20,14 +20,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "usage_vm_instance") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVMSnapshotVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVMSnapshotVO.java index 3bb354fd7cf2..c17a6dd2a12d 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVMSnapshotVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVMSnapshotVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVO.java index b1f6d295fdd6..97ddfb28e9ba 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.TimeZone; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.DateUtil; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVPNUserVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVPNUserVO.java index 9bd8e2f66837..6d82223e681b 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVPNUserVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVPNUserVO.java @@ -20,14 +20,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "usage_vpn_user") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVmDiskVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVmDiskVO.java index 16b89755da81..67ba3422a2ff 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVmDiskVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVmDiskVO.java @@ -16,10 +16,10 @@ // under the License. package com.cloud.usage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "usage_vm_disk") diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java index 6d5315e33464..7717b12e3a2b 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVpcVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVpcVO.java index e676b2bc2e98..f7202e8dd25b 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVpcVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVpcVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.usage; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; import java.util.Date; diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java index 3f852b0cfb5a..625ae9736801 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -94,13 +94,9 @@ public List getUsageRecords(Long accountId, Date startDate, Date pstmt = txn.prepareAutoCloseStatement(GET_USAGE_RECORDS_BY_ACCOUNT); pstmt.setLong(i++, accountId); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindEnd(pstmt, i); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageDateRangeBinder.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDateRangeBinder.java new file mode 100644 index 000000000000..7bde99a6d97d --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageDateRangeBinder.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.usage.dao; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Date; +import java.util.TimeZone; + +import com.cloud.utils.DateUtil; + +final class UsageDateRangeBinder { + private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); + + private final String startDate; + private final String endDate; + + private UsageDateRangeBinder(Date startDate, Date endDate) { + this.startDate = DateUtil.getDateDisplayString(GMT_TIME_ZONE, startDate); + this.endDate = DateUtil.getDateDisplayString(GMT_TIME_ZONE, endDate); + } + + static UsageDateRangeBinder of(Date startDate, Date endDate) { + return new UsageDateRangeBinder(startDate, endDate); + } + + int bindEnd(PreparedStatement pstmt, int parameterIndex) throws SQLException { + pstmt.setString(parameterIndex++, endDate); + return parameterIndex; + } + + int bindStartEnd(PreparedStatement pstmt, int parameterIndex) throws SQLException { + pstmt.setString(parameterIndex++, startDate); + pstmt.setString(parameterIndex++, endDate); + return parameterIndex; + } + + int bindStartEndPairs(PreparedStatement pstmt, int parameterIndex, int pairCount) throws SQLException { + for (int i = 0; i < pairCount; i++) { + parameterIndex = bindStartEnd(pstmt, parameterIndex); + } + return parameterIndex; + } +} diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageLoadBalancerPolicyDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageLoadBalancerPolicyDaoImpl.java index ba5c70fbc32e..92ec7bcfc7bc 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageLoadBalancerPolicyDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageLoadBalancerPolicyDaoImpl.java @@ -127,12 +127,8 @@ public List getUsageRecords(Long accountId, Long doma if (param1 != null) { pstmt.setLong(i++, param1); } - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkOfferingDaoImpl.java index b3bc06e8af40..081aec596c6f 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworkOfferingDaoImpl.java @@ -111,12 +111,8 @@ public List getUsageRecords(Long accountId, Long domainI if (param1 != null) { pstmt.setLong(i++, param1); } - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java index e7ae622ae54f..d883aca00152 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageNetworksDaoImpl.java @@ -27,7 +27,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageSecurityGroupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageSecurityGroupDaoImpl.java index 43224918f0c4..d715627bb63c 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageSecurityGroupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageSecurityGroupDaoImpl.java @@ -108,12 +108,8 @@ public List getUsageRecords(Long accountId, Long domainId, if (param1 != null) { pstmt.setLong(i++, param1); } - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); try(ResultSet rs = pstmt.executeQuery();) { while (rs.next()) { //zoneId, account_id, domain_id, vm_instance_id, security_group_id, created, deleted diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java index f863cd1e3a35..624688e8bd60 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java @@ -173,12 +173,8 @@ public List getUsageRecords(Long accountId, Long domainId, Date if (param1 != null) { pstmt.setLong(i++, param1); } - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); try(ResultSet rs = pstmt.executeQuery();) { while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVPNUserDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVPNUserDaoImpl.java index fa6f896df4a4..16831e0315b7 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVPNUserDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVPNUserDaoImpl.java @@ -106,12 +106,8 @@ public List getUsageRecords(Long accountId, Long domainId, Date if (param1 != null) { pstmt.setLong(i++, param1); } - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java index 095070feac1c..9439d6f8ca6a 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java @@ -21,10 +21,9 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.TimeZone; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; @@ -93,12 +92,8 @@ public List getUsageRecords(Long accountId, Long domainId, Date s if (param1 != null) { pstmt.setLong(i++, param1); } - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java index b5d8e46ef092..f77737dfcb46 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVpcDaoImpl.java @@ -24,13 +24,12 @@ import com.cloud.utils.db.TransactionLegacy; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.TimeZone; @Component public class UsageVpcDaoImpl extends GenericDaoBase implements UsageVpcDao { @@ -97,13 +96,9 @@ public List getUsageRecords(Long accountId, Date startDate, Date end pstmt = txn.prepareAutoCloseStatement(GET_USAGE_RECORDS_BY_ACCOUNT); pstmt.setLong(i++, accountId); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); - pstmt.setString(i++, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + UsageDateRangeBinder dateRangeBinder = UsageDateRangeBinder.of(startDate, endDate); + i = dateRangeBinder.bindEnd(pstmt, i); + i = dateRangeBinder.bindStartEndPairs(pstmt, i, 3); ResultSet rs = pstmt.executeQuery(); while (rs.next()) { diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java index aa6e49666dd4..5b02edccc9d8 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.user; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java index cbacf9af5721..18a9a5002f11 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.Optional; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; diff --git a/engine/schema/src/main/java/com/cloud/user/AccountVO.java b/engine/schema/src/main/java/com/cloud/user/AccountVO.java index 74a538565d77..4f871b8f06a7 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountVO.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountVO.java @@ -20,14 +20,14 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java b/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java index 00feda5fe8ad..4a8ff55abeb3 100644 --- a/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java +++ b/engine/schema/src/main/java/com/cloud/user/SSHKeyPairVO.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.user; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import java.util.UUID; @Entity diff --git a/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java b/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java index 7345eeb48539..4a442998aed0 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java @@ -20,17 +20,17 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/user/UserDataVO.java b/engine/schema/src/main/java/com/cloud/user/UserDataVO.java index e8864976069d..633fa323738b 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserDataVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserDataVO.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.user; -import javax.persistence.Basic; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/user/UserStatisticsVO.java b/engine/schema/src/main/java/com/cloud/user/UserStatisticsVO.java index 8f71af5dd21f..22e1cfe121c5 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserStatisticsVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserStatisticsVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.user; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/user/UserStatsLogVO.java b/engine/schema/src/main/java/com/cloud/user/UserStatsLogVO.java index c96f3d735975..a99c86b833e8 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserStatsLogVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserStatsLogVO.java @@ -20,14 +20,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "op_user_stats_log") diff --git a/engine/schema/src/main/java/com/cloud/user/UserVO.java b/engine/schema/src/main/java/com/cloud/user/UserVO.java index 1b89bc215cf7..7f315dd5c92d 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/user/VmDiskStatisticsVO.java b/engine/schema/src/main/java/com/cloud/user/VmDiskStatisticsVO.java index e5925bb43e7e..40842f3f3835 100644 --- a/engine/schema/src/main/java/com/cloud/user/VmDiskStatisticsVO.java +++ b/engine/schema/src/main/java/com/cloud/user/VmDiskStatisticsVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.user; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "vm_disk_statistics") diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java index de60e48dff8f..8af5cc9df133 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java @@ -19,7 +19,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/util/StoragePoolTypeConverter.java b/engine/schema/src/main/java/com/cloud/util/StoragePoolTypeConverter.java index ab4148ec8341..7a76f0367220 100644 --- a/engine/schema/src/main/java/com/cloud/util/StoragePoolTypeConverter.java +++ b/engine/schema/src/main/java/com/cloud/util/StoragePoolTypeConverter.java @@ -18,8 +18,8 @@ import com.cloud.storage.Storage.StoragePoolType; -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; /** * Converts {@link StoragePoolType} to and from {@link String} using {@link StoragePoolType#name()}. diff --git a/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java b/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java index 8f47ce0583db..020c866da1fc 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleProxyVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import com.cloud.hypervisor.Hypervisor.HypervisorType; diff --git a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java index d8f2838dd477..aab9b7ce1b05 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java @@ -23,14 +23,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "console_session") diff --git a/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java b/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java index 1a619734ff39..23f01f711621 100644 --- a/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/DomainRouterVO.java @@ -16,13 +16,13 @@ // under the License. package com.cloud.vm; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.network.router.VirtualRouter; diff --git a/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java b/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java index 9a8a769f0a56..29e86370c75d 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java @@ -20,14 +20,14 @@ import org.apache.cloudstack.vm.ImportVmTask; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVMMapVO.java b/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVMMapVO.java index 067f6dec78f1..e5f153842873 100644 --- a/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVMMapVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVMMapVO.java @@ -16,15 +16,15 @@ // under the License. package com.cloud.vm; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.SecondaryTables; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.SecondaryTables; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVO.java b/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVO.java index d5bd8c5aaae9..a0dadf7661cb 100644 --- a/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/InstanceGroupVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.Table; import com.cloud.user.Account; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/com/cloud/vm/ItWorkVO.java b/engine/schema/src/main/java/com/cloud/vm/ItWorkVO.java index 21e3fc67431f..aa118f2ec183 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ItWorkVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ItWorkVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.vm; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.time.InaccurateClock; import com.cloud.vm.VirtualMachine.State; diff --git a/engine/schema/src/main/java/com/cloud/vm/NicDetailVO.java b/engine/schema/src/main/java/com/cloud/vm/NicDetailVO.java index 7dc72159f3fb..74e371c22e76 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.vm; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/vm/NicExtraDhcpOptionVO.java b/engine/schema/src/main/java/com/cloud/vm/NicExtraDhcpOptionVO.java index 7b6d28fb5ffc..903773acc11b 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicExtraDhcpOptionVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicExtraDhcpOptionVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.vm; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.UUID; @Entity diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index 65946b8d8210..2c6163c40e90 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -20,15 +20,15 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.builder.EqualsBuilder; diff --git a/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java b/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java index 37a312ff78f3..fb349594986f 100644 --- a/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/SecondaryStorageVmVO.java @@ -18,15 +18,15 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.hypervisor.Hypervisor.HypervisorType; diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmCloneSettingVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmCloneSettingVO.java index f50807e4ed3f..16b1e6c8033c 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmCloneSettingVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmCloneSettingVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "user_vm_clone_setting") diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java index ce3a9a84a34f..6465a6072465 100644 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/UserVmVO.java @@ -18,13 +18,13 @@ import java.util.HashMap; -import javax.persistence.Basic; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Basic; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.uservm.UserVm; diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java index 7879aa24556b..edbf198ed4ff 100755 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.vm; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java index 9d5e1b0ff500..93910a3de344 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceVO.java @@ -25,21 +25,21 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.util.HypervisorTypeConverter; diff --git a/engine/schema/src/main/java/com/cloud/vm/VmStatsVO.java b/engine/schema/src/main/java/com/cloud/vm/VmStatsVO.java index e34ed6251034..c8cde1ba18b9 100644 --- a/engine/schema/src/main/java/com/cloud/vm/VmStatsVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VmStatsVO.java @@ -18,12 +18,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java index 63cdc042b26f..63f6538fca16 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/DomainRouterDaoImpl.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java index da9c391af9db..fc06a1fe9f87 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java @@ -26,7 +26,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.List; @Component diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index 78966a09e97c..76f954da27bb 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -20,8 +20,8 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java index 05d73c810e3b..90596206d4d2 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicIpAliasVO.java @@ -19,14 +19,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import com.cloud.utils.net.NetUtils; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java index 4c8208b4be84..98213e9d586d 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import com.cloud.vm.NicSecondaryIp; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmCloneSettingDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmCloneSettingDaoImpl.java index 344f4e86fed5..432716d995f6 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmCloneSettingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmCloneSettingDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java index 761053a89f0c..af86f7f319cd 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java @@ -28,8 +28,8 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import com.cloud.configuration.Resource; import com.cloud.utils.db.Transaction; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java index 019d152ce5c3..3331c3fe0eea 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java @@ -28,8 +28,8 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ApiConstants; import org.apache.commons.collections.CollectionUtils; diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VmStatsDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VmStatsDaoImpl.java index 327acec0c179..2ece48a94f64 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VmStatsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VmStatsDaoImpl.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotDetailsVO.java b/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotDetailsVO.java index b7a1c7bd3b15..5b0144158e12 100644 --- a/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotDetailsVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotDetailsVO.java @@ -18,12 +18,12 @@ */ package com.cloud.vm.snapshot; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotVO.java b/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotVO.java index 5b6f97b82e70..7c8854432417 100644 --- a/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/snapshot/VMSnapshotVO.java @@ -20,18 +20,18 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.cloudstack.engine.subsystem.api.storage.VMSnapshotOptions; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java index 7972fe6bc624..5de040c1c796 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java @@ -18,9 +18,9 @@ import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; @Entity @Table(name = "api_keypair_permissions") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java index eb38b08f6151..4110d8ed8459 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java @@ -22,14 +22,14 @@ import java.time.Instant; import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.Objects; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java index c700f8478bb9..ce7b4bc941e3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRolePermissionVO.java @@ -17,9 +17,9 @@ package org.apache.cloudstack.acl; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; @Entity @Table(name = "project_role_permissions") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java index 6ab2e7d7e235..afa640f96710 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ProjectRoleVO.java @@ -20,12 +20,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java index 588fa0299649..ae90c22df3de 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java @@ -20,13 +20,13 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.Column; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; @MappedSuperclass public class RolePermissionBaseVO implements RolePermissionEntity { diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java index abce83ca4f5a..44d21fc4be4d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionVO.java @@ -17,9 +17,9 @@ package org.apache.cloudstack.acl; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; @Entity @Table(name = "role_permissions") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java index cff139a9263a..77f84f884ea0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RoleVO.java @@ -20,14 +20,14 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java index 7802265928ed..b5a431c4c364 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/RolePermissionsDaoImpl.java @@ -17,7 +17,6 @@ package org.apache.cloudstack.acl.dao; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -66,35 +65,6 @@ public RolePermissionsDaoImpl() { assert (sortOrderAttribute != null) : "Couldn't find one of these attributes"; } - private boolean updateSortOrder(final RolePermissionVO permissionBeingMoved, final RolePermissionVO parentPermission) { - if (parentPermission != null && permissionBeingMoved.getId() == parentPermission.getId()) { - return true; - } - final List newOrderedPermissionsList = new ArrayList<>(); - // Null parent implies item needs to move to the top - if (parentPermission == null) { - newOrderedPermissionsList.add(permissionBeingMoved); - } - for (final RolePermissionVO permission : findAllByRoleIdSorted(permissionBeingMoved.getRoleId())) { - if (permission.getId() == permissionBeingMoved.getId()) { - continue; - } - newOrderedPermissionsList.add(permission); - if (parentPermission != null && permission.getId() == parentPermission.getId()) { - newOrderedPermissionsList.add(permissionBeingMoved); - } - } - long sortOrder = 0L; - for (final RolePermissionVO permission : newOrderedPermissionsList) { - permission.setSortOrder(sortOrder++); - if (!update(permission.getId(), permission)) { - logger.warn("Failed to update item's sort order with id:" + permission.getId() + " while moving permission with id:" + permissionBeingMoved.getId() + " to a new position"); - return false; - } - } - return true; - } - @Override public RolePermissionVO persist(final RolePermissionVO item) { item.setSortOrder(0); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupDomainMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupDomainMapVO.java index e1357cdc2324..9e92104f6e1c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupDomainMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupDomainMapVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.affinity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java index f03357046ed9..14c90498eafb 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVMMapVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.affinity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVO.java index 9b8fc5981719..b35fde18211a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/AffinityGroupVO.java @@ -18,14 +18,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java index 5bd598f36a0f..079ed9f239fa 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDaoImpl.java @@ -18,8 +18,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java index 3efedd826dca..ed2e589e3a28 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupDomainMapDaoImpl.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.cloudstack.affinity.AffinityGroupDomainMapVO; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java index f66f12646970..2a39a143d1cb 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/affinity/dao/AffinityGroupVMMapDaoImpl.java @@ -18,8 +18,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupVMMapVO; import org.apache.cloudstack.affinity.AffinityGroupVO; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java index 0d34bc0156dd..2ef6f6a8b67d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/annotation/AnnotationVO.java @@ -18,12 +18,12 @@ import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java index aaf63518708c..d98cb24ffb7b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.backup; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java index 6bdf7602a9d4..ac0010e85901 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.backup; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java index ebeb7d4a2d59..9e8eb985e728 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java @@ -22,14 +22,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "backup_offering") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java index 1764496a6c0b..66dc3a52d0f5 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java @@ -22,14 +22,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "backup_repository") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java index 1ee2cff78b65..583b39f24dcc 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.DateUtil; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index 0f8a10fb7be6..4f8098e82f9e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -30,17 +30,17 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; @Entity @Table(name = "backups") @@ -103,6 +103,18 @@ public class BackupVO implements Backup { @Column(name = "backup_schedule_id") private Long backupScheduleId; + @Column(name = "from_checkpoint_id") + private String fromCheckpointId; + + @Column(name = "to_checkpoint_id") + private String toCheckpointId; + + @Column(name = "checkpoint_create_time") + private Long checkpointCreateTime; + + @Column(name = "host_id") + private Long hostId; + @Transient Map details; @@ -288,4 +300,40 @@ public Long getBackupScheduleId() { public void setBackupScheduleId(Long backupScheduleId) { this.backupScheduleId = backupScheduleId; } + + @Override + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public void setFromCheckpointId(String fromCheckpointId) { + this.fromCheckpointId = fromCheckpointId; + } + + @Override + public String getToCheckpointId() { + return toCheckpointId; + } + + public void setToCheckpointId(String toCheckpointId) { + this.toCheckpointId = toCheckpointId; + } + + @Override + public Long getCheckpointCreateTime() { + return checkpointCreateTime; + } + + public void setCheckpointCreateTime(Long checkpointCreateTime) { + this.checkpointCreateTime = checkpointCreateTime; + } + + @Override + public Long getHostId() { + return hostId; + } + + public void setHostId(Long hostId) { + this.hostId = hostId; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java new file mode 100644 index 000000000000..6525731d3d1e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java @@ -0,0 +1,242 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.Date; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; + +@Entity +@Table(name = "image_transfer") +public class ImageTransferVO implements ImageTransfer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "backup_id") + private Long backupId; + + @Column(name = "volume_id") + private long volumeId; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "socket") + private String socket; + + @Column(name = "file") + private String file; + + @Column(name = "transfer_url") + private String transferUrl; + + @Enumerated(value = EnumType.STRING) + @Column(name = "phase") + private Phase phase; + + @Enumerated(value = EnumType.STRING) + @Column(name = "direction") + private Direction direction; + + @Enumerated(value = EnumType.STRING) + @Column(name = "backend") + private Backend backend; + + @Column(name = "signed_ticket_id") + private String signedTicketId; + + @Column(name = "account_id") + Long accountId; + + @Column(name = "domain_id") + Long domainId; + + @Column(name = "data_center_id") + Long dataCenterId; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = "updated") + @Temporal(value = TemporalType.TIMESTAMP) + private Date updated; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public ImageTransferVO() { + } + + private ImageTransferVO(String uuid, long volumeId, long hostId, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) { + this.uuid = uuid; + this.volumeId = volumeId; + this.hostId = hostId; + this.phase = phase; + this.direction = direction; + this.accountId = accountId; + this.domainId = domainId; + this.dataCenterId = dataCenterId; + this.created = new Date(); + } + + public ImageTransferVO(String uuid, Long backupId, long volumeId, long hostId, String socket, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) { + this(uuid, volumeId, hostId, phase, direction, accountId, domainId, dataCenterId); + this.backupId = backupId; + this.socket = socket; + this.backend = Backend.nbd; + } + + public ImageTransferVO(String uuid, long volumeId, long hostId, String file, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) { + this(uuid, volumeId, hostId, phase, direction, accountId, domainId, dataCenterId); + this.file = file; + this.backend = Backend.file; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public Long getBackupId() { + return backupId; + } + + public void setBackupId(long backupId) { + this.backupId = backupId; + } + + @Override + public long getVolumeId() { + return volumeId; + } + + public void setVolumeId(long volumeId) { + this.volumeId = volumeId; + } + + @Override + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + public void setSocket(String socket) { + this.socket = socket; + } + + @Override + public String getTransferUrl() { + return transferUrl; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } + + @Override + public Phase getPhase() { + return phase; + } + + public void setPhase(Phase phase) { + this.phase = phase; + this.updated = new Date(); + } + + @Override + public Direction getDirection() { + return direction; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + @Override + public Backend getBackend() { + return backend; + } + + @Override + public String getSignedTicketId() { + return signedTicketId; + } + + public void setSignedTicketId(String signedTicketId) { + this.signedTicketId = signedTicketId; + } + + @Override + public Class getEntityType() { + return ImageTransfer.class; + } + + @Override + public String getName() { + return null; + } + + @Override + public long getDomainId() { + return domainId; + } + + @Override + public long getAccountId() { + return accountId; + } + + @Override + public long getDataCenterId() { + return dataCenterId; + } + + public Date getCreated() { + return created; + } + + public Date getUpdated() { + return updated; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java index fd29da72c718..937ad792792e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -21,8 +21,8 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.dao.VMTemplateDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index 708faeef4643..e4432797c152 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -17,8 +17,8 @@ package org.apache.cloudstack.backup.dao; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java index ea969988e2bb..afefe818cfaa 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java @@ -20,8 +20,8 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.backup.BackupOfferingVO; import org.apache.cloudstack.backup.BackupRepository; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index 972af73391af..0084194e5293 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -22,7 +22,7 @@ import java.util.Date; import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import com.cloud.utils.DateUtil; import com.cloud.utils.db.DB; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java new file mode 100644 index 000000000000..9a9fbeb8814e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java @@ -0,0 +1,36 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup.dao; + +import java.util.List; + +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.ImageTransferVO; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDao; + +public interface ImageTransferDao extends GenericDao { + List listByBackupId(Long backupId); + ImageTransferVO findByUuid(String uuid); + ImageTransferVO findByVolume(Long volumeId); + ImageTransferVO findUnfinishedByVolume(Long volumeId); + List listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction); + List listByZonesAndOwners(List zoneIds, List accountIds, List domainIds, + Filter filter); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java new file mode 100644 index 000000000000..c450121a8ed7 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java @@ -0,0 +1,140 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup.dao; + +import java.util.Collections; +import java.util.List; + +import jakarta.annotation.PostConstruct; + +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.ImageTransferVO; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +public class ImageTransferDaoImpl extends GenericDaoBase implements ImageTransferDao { + + private SearchBuilder backupIdSearch; + private SearchBuilder uuidSearch; + private SearchBuilder volumeSearch; + private SearchBuilder volumeUnfinishedSearch; + private SearchBuilder phaseDirectionSearch; + + public ImageTransferDaoImpl() { + } + + @PostConstruct + protected void init() { + backupIdSearch = createSearchBuilder(); + backupIdSearch.and("backupId", backupIdSearch.entity().getBackupId(), SearchCriteria.Op.EQ); + backupIdSearch.done(); + + uuidSearch = createSearchBuilder(); + uuidSearch.and("uuid", uuidSearch.entity().getUuid(), SearchCriteria.Op.EQ); + uuidSearch.done(); + + volumeSearch = createSearchBuilder(); + volumeSearch.and("volumeId", volumeSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + volumeSearch.done(); + + volumeUnfinishedSearch = createSearchBuilder(); + volumeUnfinishedSearch.and("volumeId", volumeUnfinishedSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + volumeUnfinishedSearch.and("phase", volumeUnfinishedSearch.entity().getPhase(), SearchCriteria.Op.NEQ); + volumeUnfinishedSearch.done(); + + phaseDirectionSearch = createSearchBuilder(); + phaseDirectionSearch.and("phase", phaseDirectionSearch.entity().getPhase(), SearchCriteria.Op.EQ); + phaseDirectionSearch.and("direction", phaseDirectionSearch.entity().getDirection(), SearchCriteria.Op.EQ); + phaseDirectionSearch.done(); + } + + @Override + public List listByBackupId(Long backupId) { + SearchCriteria sc = backupIdSearch.create(); + sc.setParameters("backupId", backupId); + return listBy(sc); + } + + @Override + public ImageTransferVO findByUuid(String uuid) { + SearchCriteria sc = uuidSearch.create(); + sc.setParameters("uuid", uuid); + return findOneBy(sc); + } + + @Override + public ImageTransferVO findByVolume(Long volumeId) { + SearchCriteria sc = volumeSearch.create(); + sc.setParameters("volumeId", volumeId); + return findOneBy(sc); + } + + @Override + public ImageTransferVO findUnfinishedByVolume(Long volumeId) { + SearchCriteria sc = volumeUnfinishedSearch.create(); + sc.setParameters("volumeId", volumeId); + sc.setParameters("phase", ImageTransferVO.Phase.finished.toString()); + return findOneBy(sc); + } + + @Override + public List listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction) { + SearchCriteria sc = phaseDirectionSearch.create(); + sc.setParameters("phase", phase); + sc.setParameters("direction", direction); + return listBy(sc); + } + + @Override + public List listByZonesAndOwners(List zoneIds, List accountIds, List domainIds, + Filter filter) { + if (CollectionUtils.isEmpty(zoneIds)) { + return Collections.emptyList(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.IN); + boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds); + boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds); + if (accountIdsNotEmpty && domainIdsNotEmpty) { + sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN); + sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN); + sb.cp(); + } else if (accountIdsNotEmpty) { + sb.and("account", sb.entity().getAccountId(), SearchCriteria.Op.IN); + } else if (domainIdsNotEmpty) { + sb.and("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN); + } + sb.done(); + final SearchCriteria sc = sb.create(); + sc.setParameters("dataCenterId", zoneIds.toArray()); + if (accountIdsNotEmpty) { + sc.setParameters("account", accountIds.toArray()); + } + if (domainIdsNotEmpty) { + sc.setParameters("domain", domainIds.toArray()); + } + + return listBy(sc, filter); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.java index 6afc2e7707a1..def1faf110c3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanMigrationVO.java @@ -22,12 +22,12 @@ import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "cluster_drs_plan_migration") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java index 68f7fe4b44e8..a59b87123459 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/cluster/ClusterDrsPlanVO.java @@ -22,12 +22,12 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java b/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java index 150c9662ada1..0ab69069cd7a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java @@ -19,14 +19,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.agent.api.Command; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/datacenter/DataCenterIpv4GuestSubnetVO.java b/engine/schema/src/main/java/org/apache/cloudstack/datacenter/DataCenterIpv4GuestSubnetVO.java index 828e7b39e9a4..b583ee346000 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/datacenter/DataCenterIpv4GuestSubnetVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/datacenter/DataCenterIpv4GuestSubnetVO.java @@ -20,12 +20,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java index f1515e26e9ee..84e9ea9670bd 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateHostMapVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.direct.download; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "direct_download_certificate_host_map") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java index 3c35f59659f6..81024b703be5 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/direct/download/DirectDownloadCertificateVO.java @@ -20,13 +20,13 @@ import org.apache.cloudstack.util.HypervisorTypeConverter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.UUID; @Entity diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMComputeTagVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMComputeTagVO.java index ae6a807f5005..9cfc297e423b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMComputeTagVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMComputeTagVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.engine.cloud.entity.api.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java index 917f8bb800a2..8e089fbc1aa1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMEntityVO.java @@ -23,21 +23,21 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.cloudstack.backup.Backup; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMNetworkMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMNetworkMapVO.java index ef42d0716cbe..bcda7aed4f1c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMNetworkMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMNetworkMapVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.engine.cloud.entity.api.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMReservationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMReservationVO.java index b934a5d6118c..ffdb2555e879 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMReservationVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMReservationVO.java @@ -20,13 +20,13 @@ import java.util.Map; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMRootDiskTagVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMRootDiskTagVO.java index 533aaf7e80e9..a60c9995300e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMRootDiskTagVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VMRootDiskTagVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.engine.cloud.entity.api.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java index 0452d0f5d74e..0550b58cd81f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/VolumeReservationVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.engine.cloud.entity.api.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMComputeTagDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMComputeTagDaoImpl.java index 7f2410e6ba0c..e617f4542a34 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMComputeTagDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMComputeTagDaoImpl.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMEntityDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMEntityDaoImpl.java index a894e87bd6da..9aac2efee054 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMEntityDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMEntityDaoImpl.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMNetworkMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMNetworkMapDaoImpl.java index bb26d382ef77..2a4f757ce245 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMNetworkMapDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMNetworkMapDaoImpl.java @@ -19,8 +19,8 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMReservationDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMReservationDaoImpl.java index 871425bb78b0..ec18b6bc0ed0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMReservationDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMReservationDaoImpl.java @@ -20,8 +20,8 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMRootDiskTagDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMRootDiskTagDaoImpl.java index 3e4eaf1c9aa8..8c946b077602 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMRootDiskTagDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VMRootDiskTagDaoImpl.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java index 7069594d1668..f0549fe74c7e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/engine/cloud/entity/api/db/dao/VolumeReservationDaoImpl.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.springframework.stereotype.Component; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java index 046a19c59fa0..ef6e75c6a50b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.gui.theme; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "gui_themes_details") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java index 2df23b3d1064..9ada7b1c1097 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java @@ -18,12 +18,12 @@ import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; @Entity diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java index 887e3886f6c6..ad6225ef3ce2 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java @@ -19,14 +19,14 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java index b0969833eb01..b9c60fe9a7a0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.gui.theme.GuiThemeVO; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java index ce6f70558128..95d9f0a2a474 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java @@ -27,7 +27,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/ha/HAConfigVO.java b/engine/schema/src/main/java/org/apache/cloudstack/ha/HAConfigVO.java index a68ed93746d5..c99d57cf9bc6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/ha/HAConfigVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/ha/HAConfigVO.java @@ -19,16 +19,16 @@ import com.cloud.utils.db.StateMachine; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; @Entity diff --git a/engine/schema/src/main/java/org/apache/cloudstack/lb/ApplicationLoadBalancerRuleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/lb/ApplicationLoadBalancerRuleVO.java index 4fec96067a36..37a281b54254 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/lb/ApplicationLoadBalancerRuleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/lb/ApplicationLoadBalancerRuleVO.java @@ -17,13 +17,13 @@ package org.apache.cloudstack.lb; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerDetailsVO.java index 9e3378870113..cf2c405ceaad 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerDetailsVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerDetailsVO.java @@ -16,14 +16,14 @@ // under the License. package org.apache.cloudstack.network; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerNetworkMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerNetworkMapVO.java index b520ecd5cd1a..fe7abf8245f1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerNetworkMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerNetworkMapVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.network; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerVO.java b/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerVO.java index c60a3ec38683..7c5ba20c2ea5 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/BgpPeerVO.java @@ -20,12 +20,12 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/Ipv4GuestSubnetNetworkMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/network/Ipv4GuestSubnetNetworkMapVO.java index cc726ba3d357..9cceac2dcf02 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/Ipv4GuestSubnetNetworkMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/Ipv4GuestSubnetNetworkMapVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.utils.db.GenericDao; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/NetworkPermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/network/NetworkPermissionVO.java index fd75db9bbd74..b43dcaac1965 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/NetworkPermissionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/NetworkPermissionVO.java @@ -16,10 +16,10 @@ // under the License. package org.apache.cloudstack.network; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.network.NetworkPermission; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerDaoImpl.java index 0f95f7c3cd58..5cc59ae5726a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerDaoImpl.java @@ -32,8 +32,8 @@ import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerNetworkMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerNetworkMapDaoImpl.java index a5e5f47684a6..9f9889938d1b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerNetworkMapDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/dao/BgpPeerNetworkMapDaoImpl.java @@ -18,8 +18,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/network/dao/Ipv4GuestSubnetNetworkMapDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/network/dao/Ipv4GuestSubnetNetworkMapDaoImpl.java index 95e53448907e..d2ccf2d3cb99 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/network/dao/Ipv4GuestSubnetNetworkMapDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/network/dao/Ipv4GuestSubnetNetworkMapDaoImpl.java @@ -19,8 +19,8 @@ import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap; import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMapVO; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java b/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java index 2f975caf0f17..3ac734d2ac44 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/outofbandmanagement/OutOfBandManagementVO.java @@ -20,16 +20,16 @@ import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.StateMachine; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; @Entity diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeVO.java index 338ec6c47c09..15923073546b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpRangeVO.java @@ -18,12 +18,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "portable_ip_range") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpVO.java index 06d0508bede7..7cc036f46a48 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/region/PortableIpVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "portable_ip_address") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/RegionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/RegionVO.java index 608bd2b15874..d39c1b27ac3d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/region/RegionVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/region/RegionVO.java @@ -16,10 +16,10 @@ // under the License. package org.apache.cloudstack.region; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "region") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapVO.java index 2a4570e7aa99..d392999757c4 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerLbRuleMapVO.java @@ -17,12 +17,12 @@ package org.apache.cloudstack.region.gslb; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java index 4ce7033156fa..0140221d0e93 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java @@ -19,14 +19,14 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.region.ha.GlobalLoadBalancerRule; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/reservation/ReservationVO.java b/engine/schema/src/main/java/org/apache/cloudstack/reservation/ReservationVO.java index df0ede6821ad..958c1f54e2d1 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/reservation/ReservationVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/reservation/ReservationVO.java @@ -18,12 +18,12 @@ // package org.apache.cloudstack.reservation; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.user.ResourceReservation; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmGroupDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmGroupDetailVO.java index febcca011c77..53c3037efd6a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmGroupDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmGroupDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmProfileDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmProfileDetailVO.java index 6d9dfd78a9b4..7f0283a232a6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmProfileDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/AutoScaleVmProfileDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java index 7b0500680b95..26c369a9291f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/DiskOfferingDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java index 1149d0b13e77..34991a39af26 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/FirewallRuleDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/GuestOsDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/GuestOsDetailVO.java index 0ab252afa38e..889a1ddaa1b6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/GuestOsDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/GuestOsDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBHealthCheckPolicyDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBHealthCheckPolicyDetailVO.java index 52b30ff18b44..16f1d149ea89 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBHealthCheckPolicyDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBHealthCheckPolicyDetailVO.java @@ -12,12 +12,12 @@ // Automatically generated by addcopyright.py at 04/03/2012 package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBStickinessPolicyDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBStickinessPolicyDetailVO.java index caa2d90000fc..f93eba1475a2 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBStickinessPolicyDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/LBStickinessPolicyDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLItemDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLItemDetailVO.java index d9a74ed233cc..9b680d696be9 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLItemDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLItemDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLListDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLListDetailVO.java index 49fba5ac4147..f8f29ecc1e04 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLListDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/NetworkACLListDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/RemoteAccessVpnDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/RemoteAccessVpnDetailVO.java index 5fb01a25c2a9..69a8b27b7082 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/RemoteAccessVpnDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/RemoteAccessVpnDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java index eafaed182abd..e89b3be2cafd 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; -import javax.inject.Inject; +import jakarta.inject.Inject; public abstract class ResourceDetailsDaoBase extends GenericDaoBase implements ResourceDetailsDao { diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteCustomerGatewayDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteCustomerGatewayDetailVO.java index 5c4c92d8cbb7..16b31fdece0c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteCustomerGatewayDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteCustomerGatewayDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnConnectionDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnConnectionDetailVO.java index 17e08f21d007..8afb234a82bc 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnConnectionDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnConnectionDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnGatewayDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnGatewayDetailVO.java index 9665d5f295c4..6e35fa531cdd 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnGatewayDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/Site2SiteVpnGatewayDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/SnapshotPolicyDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/SnapshotPolicyDetailVO.java index 2ee48e6b4e93..ae91fa1228e3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/SnapshotPolicyDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/SnapshotPolicyDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java index 93b49bc20a10..fc72b73657b3 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserIpAddressDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserIpAddressDetailVO.java index 80393e083b9c..ab9190de8438 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserIpAddressDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserIpAddressDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcDetailVO.java index 1952e238a089..eca551931e9f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcGatewayDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcGatewayDetailVO.java index 0f4b2aff4fd0..0e10a5f33131 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcGatewayDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/VpcGatewayDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.resourcedetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/secret/PassphraseVO.java b/engine/schema/src/main/java/org/apache/cloudstack/secret/PassphraseVO.java index 7d70eff3dc11..4369da72ea14 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/secret/PassphraseVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/secret/PassphraseVO.java @@ -22,12 +22,12 @@ import com.cloud.utils.exception.CloudRuntimeException; import org.apache.commons.lang3.StringUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java index f647d0c83656..8b90f90b464c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/HeuristicVO.java @@ -20,14 +20,14 @@ import org.apache.cloudstack.secstorage.heuristics.Heuristic; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.java index 0b51b2aec6b3..5b046a506517 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/secstorage/dao/SecondaryStorageHeuristicDaoImpl.java @@ -24,7 +24,7 @@ import org.apache.cloudstack.secstorage.heuristics.HeuristicType; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; @Component public class SecondaryStorageHeuristicDaoImpl extends GenericDaoBase implements SecondaryStorageHeuristicDao { diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailVO.java index fdfb7348fca1..89c0f1220ac6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.storage.datastore.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java index d7e88bd31c3f..61e6f6ee3b6e 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.ConfigKey; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.java index a698184c0e7d..29ab01406206 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreObjectDownloadVO.java @@ -21,12 +21,12 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.storage.ImageStoreObjectDownload; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.Date; @Entity diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreVO.java index c13f5aac6d69..598d4bbb0c8f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreVO.java @@ -20,13 +20,13 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; import com.cloud.storage.DataStoreRole; import com.cloud.storage.ImageStore; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java index 891ac0996aca..90a5f0d45f70 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import java.util.ArrayList; import java.util.List; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.java index 1f4047f8f904..a519b2f67a35 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDetailVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.ResourceDetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "object_store_details") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java index 23b650acc79c..5b9493f3aaca 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java @@ -22,12 +22,12 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Transient; import java.util.Date; import java.util.Map; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java index b5d6415e3a1a..402a1a42d2c6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.storage.StoragePoolAndAccessGroupMapVO; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDetailVO.java index 5351959ea1ac..514df7e64602 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.storage.datastore.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java index 8b7a2b78de7e..95be807ec0f4 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java @@ -38,7 +38,7 @@ import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import java.sql.PreparedStatement; import java.sql.ResultSet; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java index 44eb7e6c02cb..c9362669ae15 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.BooleanUtils; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailVO.java index 8c1428bbd157..aabb1faea411 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolDetailVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.storage.datastore.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java index c2f5d0a5d96f..1649da6adf53 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/StoragePoolVO.java @@ -28,16 +28,16 @@ import org.apache.cloudstack.util.HypervisorTypeConverter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.TableGenerator; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java index a6e7a5a4fea7..b2d1e3e44740 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreVO.java index c475a4203a73..992151beeeec 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/VolumeDataStoreVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSVO.java index 8870bf6d4d89..193c2853ec08 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSVO.java @@ -25,16 +25,16 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; @Entity @Table(name = "shared_filesystem") diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java index da6220716715..5bee0757b974 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java @@ -26,7 +26,7 @@ import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.storage.sharedfs.SharedFSVO; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; import java.util.List; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java b/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java index 8e56cce739d8..cecfc225bfe7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/util/CPUArchConverter.java @@ -18,8 +18,8 @@ import com.cloud.cpu.CPU; -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; @Converter(autoApply = true) public class CPUArchConverter implements AttributeConverter { diff --git a/engine/schema/src/main/java/org/apache/cloudstack/util/HypervisorTypeConverter.java b/engine/schema/src/main/java/org/apache/cloudstack/util/HypervisorTypeConverter.java index 57c12a9b7faa..5d073dafb654 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/util/HypervisorTypeConverter.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/util/HypervisorTypeConverter.java @@ -18,8 +18,8 @@ import com.cloud.hypervisor.Hypervisor; -import javax.persistence.AttributeConverter; -import javax.persistence.Converter; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; /** * Converts {@link com.cloud.hypervisor.Hypervisor.HypervisorType} to and from {@link String} using {@link com.cloud.hypervisor.Hypervisor.HypervisorType#name()}. diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java index e0065db1e77a..0b5099f2ad2b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java @@ -21,16 +21,16 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.time.ZoneId; import java.util.Date; import java.util.TimeZone; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java index 775e9cfe40cf..3a69516f963d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java @@ -20,16 +20,16 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index ad3722577c27..f365091bf6ec 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -271,6 +271,7 @@ + diff --git a/engine/schema/src/main/resources/META-INF/db/create-default-role-api-mappings.sql b/engine/schema/src/main/resources/META-INF/db/create-default-role-api-mappings.sql index d814b0fc298e..0c42ab75bd7b 100644 --- a/engine/schema/src/main/resources/META-INF/db/create-default-role-api-mappings.sql +++ b/engine/schema/src/main/resources/META-INF/db/create-default-role-api-mappings.sql @@ -157,10 +157,6 @@ INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'extractVolume', 'ALLOW', 134) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getApiLimit', 'ALLOW', 135) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getCloudIdentifier', 'ALLOW', 136) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getSolidFireAccountId', 'ALLOW', 137) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getSolidFireVolumeAccessGroupId', 'ALLOW', 138) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getSolidFireVolumeIscsiName', 'ALLOW', 139) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getSolidFireVolumeSize', 'ALLOW', 140) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getUploadParamsForTemplate', 'ALLOW', 141) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getUploadParamsForVolume', 'ALLOW', 142) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 2, 'getVMPassword', 'ALLOW', 143) ON DUPLICATE KEY UPDATE rule=rule; @@ -470,10 +466,6 @@ INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'extractVolume', 'ALLOW', 130) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getApiLimit', 'ALLOW', 131) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getCloudIdentifier', 'ALLOW', 132) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getSolidFireAccountId', 'ALLOW', 133) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getSolidFireVolumeAccessGroupId', 'ALLOW', 134) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getSolidFireVolumeIscsiName', 'ALLOW', 135) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getSolidFireVolumeSize', 'ALLOW', 136) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getUploadParamsForTemplate', 'ALLOW', 137) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getUploadParamsForVolume', 'ALLOW', 138) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 3, 'getVMPassword', 'ALLOW', 139) ON DUPLICATE KEY UPDATE rule=rule; @@ -749,10 +741,6 @@ INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'extractVolume', 'ALLOW', 108) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getApiLimit', 'ALLOW', 109) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getCloudIdentifier', 'ALLOW', 110) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getSolidFireAccountId', 'ALLOW', 111) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getSolidFireVolumeAccessGroupId', 'ALLOW', 112) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getSolidFireVolumeIscsiName', 'ALLOW', 113) ON DUPLICATE KEY UPDATE rule=rule; -INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getSolidFireVolumeSize', 'ALLOW', 114) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getUploadParamsForTemplate', 'ALLOW', 115) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getUploadParamsForVolume', 'ALLOW', 116) ON DUPLICATE KEY UPDATE rule=rule; INSERT INTO `cloud`.`role_permissions` (`uuid`, `role_id`, `rule`, `permission`, `sort_order`) values (UUID(), 4, 'getVMPassword', 'ALLOW', 117) ON DUPLICATE KEY UPDATE rule=rule; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql index c99f798d3d56..8aa77096ddd2 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -131,3 +131,43 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` ( -- Add the 'keep_mac_address_on_public_nic' column to the 'cloud.networks' and 'cloud.vpc' tables CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.networks', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc', 'keep_mac_address_on_public_nic', 'TINYINT(1) NOT NULL DEFAULT 1'); + +-- Add checkpoint tracking fields to backups table for incremental backup support +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'from_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Previous active checkpoint id for incremental backups"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "New checkpoint id created for the next incremental backup"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Checkpoint creation timestamp from libvirt"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'host_id', 'BIGINT UNSIGNED DEFAULT NULL COMMENT "Host where backup is running"'); + +-- Create image_transfer table for per-disk image transfers +CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40) NOT NULL COMMENT 'uuid', + `account_id` bigint unsigned NOT NULL COMMENT 'Account ID', + `domain_id` bigint unsigned NOT NULL COMMENT 'Domain ID', + `data_center_id` bigint unsigned NOT NULL COMMENT 'Data Center ID', + `backup_id` bigint unsigned COMMENT 'Backup ID', + `volume_id` bigint unsigned NOT NULL COMMENT 'Volume ID', + `host_id` bigint unsigned NOT NULL COMMENT 'Host ID', + `transfer_url` varchar(255) COMMENT 'ImageIO transfer URL', + `file` varchar(255) COMMENT 'File for the file backend', + `phase` varchar(20) NOT NULL COMMENT 'Transfer phase: initializing, transferring, finished, failed', + `socket` varchar(255) COMMENT 'Unix socket for nbd backend', + `direction` varchar(20) NOT NULL COMMENT 'Direction: upload, download', + `backend` varchar(20) NOT NULL COMMENT 'Backend: nbd, file', + `progress` int COMMENT 'Transfer progress percentage (0-100)', + `signed_ticket_id` varchar(255) COMMENT 'Signed ticket ID from ImageIO', + `created` datetime NOT NULL COMMENT 'date created', + `updated` datetime COMMENT 'date updated if not null', + `removed` datetime COMMENT 'date removed if not null', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + CONSTRAINT `fk_image_transfer__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_image_transfer__volume_id` FOREIGN KEY (`volume_id`) REFERENCES `volumes`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_image_transfer__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE, + INDEX `i_image_transfer__backup_id`(`backup_id`), + INDEX `i_image_transfer__volume_id`(`volume_id`), + INDEX `i_image_transfer__volume_id__phase`(`volume_id`, `phase`), + INDEX `i_image_transfer__phase__direction`(`phase`, `direction`), + INDEX `i_image_transfer__data_center_id__account_id`(`data_center_id`, `account_id`), + INDEX `i_image_transfer__data_center_id__domain_id`(`data_center_id`, `domain_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java b/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java index 51db952eb613..c71b486a86c6 100644 --- a/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java +++ b/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java @@ -1555,7 +1555,6 @@ public void updateHypervisorGuestOsMap_UpdatesGuestOsMapSuccessfully() { assertEquals(10, SystemVmTemplateRegistration.hypervisorGuestOsMap.get(Hypervisor.HypervisorType.KVM).intValue()); assertEquals(10, SystemVmTemplateRegistration.hypervisorGuestOsMap.get(Hypervisor.HypervisorType.Hyperv).intValue()); assertEquals(10, SystemVmTemplateRegistration.hypervisorGuestOsMap.get(Hypervisor.HypervisorType.LXC).intValue()); - assertEquals(10, SystemVmTemplateRegistration.hypervisorGuestOsMap.get(Hypervisor.HypervisorType.Ovm3).intValue()); } @Test diff --git a/engine/schema/src/test/java/com/cloud/usage/dao/UsageDateRangeBinderTest.java b/engine/schema/src/test/java/com/cloud/usage/dao/UsageDateRangeBinderTest.java new file mode 100644 index 000000000000..4565dd0ec63f --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/usage/dao/UsageDateRangeBinderTest.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.usage.dao; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import java.sql.PreparedStatement; +import java.util.Date; +import java.util.TimeZone; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.DateUtil; + +@RunWith(MockitoJUnitRunner.class) +public class UsageDateRangeBinderTest { + + @Mock + private PreparedStatement preparedStatement; + + private final Date startDate = new Date(1715754600000L); + private final Date endDate = new Date(1715841000000L); + + @Test + public void bindStartEndPairsUsesGmtFormattedDatesAndReturnsNextIndex() throws Exception { + UsageDateRangeBinder binder = UsageDateRangeBinder.of(startDate, endDate); + + int nextIndex = binder.bindStartEndPairs(preparedStatement, 3, 2); + + assertEquals(7, nextIndex); + verify(preparedStatement).setString(3, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + verify(preparedStatement).setString(4, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + verify(preparedStatement).setString(5, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + verify(preparedStatement).setString(6, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + verifyNoMoreInteractions(preparedStatement); + } + + @Test + public void bindEndThenStartEndPairsPreservesExistingUsageDaoOrder() throws Exception { + UsageDateRangeBinder binder = UsageDateRangeBinder.of(startDate, endDate); + + int nextIndex = binder.bindEnd(preparedStatement, 2); + nextIndex = binder.bindStartEndPairs(preparedStatement, nextIndex, 3); + + assertEquals(9, nextIndex); + verify(preparedStatement).setString(2, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + verify(preparedStatement).setString(3, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + verify(preparedStatement).setString(4, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + verify(preparedStatement).setString(5, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + verify(preparedStatement).setString(6, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + verify(preparedStatement).setString(7, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), startDate)); + verify(preparedStatement).setString(8, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), endDate)); + verifyNoMoreInteractions(preparedStatement); + } +} diff --git a/engine/schema/src/test/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBaseTest.java b/engine/schema/src/test/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBaseTest.java index 4c54599c396c..410f3d63b7f5 100644 --- a/engine/schema/src/test/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBaseTest.java +++ b/engine/schema/src/test/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBaseTest.java @@ -31,12 +31,12 @@ import java.util.List; import java.util.Map; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; import org.junit.Before; diff --git a/engine/service/src/main/webapp/WEB-INF/log4j.xml b/engine/service/src/main/webapp/WEB-INF/log4j.xml index 48d61a10b413..b6f1f41fafe3 100644 --- a/engine/service/src/main/webapp/WEB-INF/log4j.xml +++ b/engine/service/src/main/webapp/WEB-INF/log4j.xml @@ -20,17 +20,37 @@ - net.sf.cglib.proxy + + org.springframework.cglib.proxy + + ${env:CLOUDSTACK_LOG_FORMAT:-text} - + - + + + + + + + + + + + + + + + diff --git a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheRandomAllocator.java b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheRandomAllocator.java index fe3bb5cf00da..3b8df5688588 100644 --- a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheRandomAllocator.java +++ b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheRandomAllocator.java @@ -20,7 +20,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java index 21ef851fb2f2..a7f78356244d 100644 --- a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java +++ b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheManagerImpl.java @@ -29,7 +29,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.logging.log4j.Logger; diff --git a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRU.java b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRU.java index 7042ee453334..d24c7918eb66 100644 --- a/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRU.java +++ b/engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRU.java @@ -21,8 +21,8 @@ import java.util.Calendar; import java.util.Date; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java index 8145158dfa40..bdee574a7e77 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.Objects; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.agent.api.to.DiskTO; import com.cloud.storage.Storage; diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java index e55302b8044d..810ec7b5276d 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/DataMotionServiceImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService; diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/HostResolutionService.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/HostResolutionService.java new file mode 100644 index 000000000000..8a8970ab662a --- /dev/null +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/HostResolutionService.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import java.util.List; + +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; + +import com.cloud.host.HostVO; +import com.cloud.hypervisor.Hypervisor.HypervisorType; + +/** + * Read-only host-resolution helpers that pick a suitable {@link HostVO} + * on which to execute storage-side operations (copy, resignature, snapshot + * chain caching, etc.) for a given {@link SnapshotInfo}, {@link VolumeInfo} + * or {@link StoragePoolVO}. + * + *

Extracted from {@link StorageSystemDataMotionStrategy} as part of + * the Phase 4 Spring-component decomposition. The strategy continues to + * expose the matching {@code getHost(...)} / {@code getHostInCluster(...)} + * methods as one-line wrappers that delegate here, so existing call sites + * and test spies keep working unchanged. + * + *

Nothing in here mutates state -- the implementations read from + * {@code ResourceManager}, {@code HostDao}, {@code ClusterDao} and + * {@code DataStoreManager} and return a single shuffled-and-filtered + * {@link HostVO}, or {@code null} when no eligible host exists. + */ +public interface HostResolutionService { + + /** + * Pick a host capable of servicing the given {@code snapshotInfo}'s + * storage. Routing depends on hypervisor type: + *

    + *
  • XenServer -- prefers a host in a cluster that supports + * resigning; falls back to any eligible enabled host. Throws + * {@link com.cloud.utils.exception.CloudRuntimeException} when + * neither lookup succeeds.
  • + *
  • VMware / KVM -- any eligible enabled host.
  • + *
  • Any other hypervisor -- throws + * {@link com.cloud.utils.exception.CloudRuntimeException}.
  • + *
+ */ + HostVO getHost(SnapshotInfo snapshotInfo); + + /** + * Return a single enabled host in the storage pool's cluster that is + * eligible for connecting to the pool. Hosts are shuffled before + * selection. Throws + * {@link com.cloud.utils.exception.CloudRuntimeException} when no + * eligible host is found. + */ + HostVO getHostInCluster(StoragePoolVO storagePool); + + /** + * Pick an eligible host for the given {@code snapshotInfo} within the + * snapshot's zone, scoped to {@code hypervisorType}. When the snapshot + * lives on primary storage the resource manager's storage-connection + * filter is used; otherwise all hosts in the zone matching the + * hypervisor are considered. May return {@code null}. + * + * @param computeClusterMustSupportResign when {@code true}, only hosts + * in clusters with {@code supportsResigning} set are returned. + */ + HostVO getHost(SnapshotInfo snapshotInfo, HypervisorType hypervisorType, boolean computeClusterMustSupportResign); + + /** + * Pick an eligible host for the given {@code volumeInfo} within the + * volume's zone, scoped to {@code hypervisorType}. When the volume + * lives on primary storage the resource manager's storage-connection + * filter is used; otherwise all hosts in the zone matching the + * hypervisor are considered. May return {@code null}. + * + * @param computeClusterMustSupportResign when {@code true}, only hosts + * in clusters with {@code supportsResigning} set are returned. + */ + HostVO getHost(VolumeInfo volumeInfo, HypervisorType hypervisorType, boolean computeClusterMustSupportResign); + + /** + * Filter and shuffle the supplied {@code hosts} list, returning the + * first enabled host (optionally requiring its cluster to support + * resigning). Returns {@code null} when {@code hosts} is {@code null} + * or no host matches. + */ + HostVO getHost(List hosts, boolean computeClusterMustSupportResign); +} diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/HostResolutionServiceImpl.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/HostResolutionServiceImpl.java new file mode 100644 index 000000000000..12e7b0e2a66d --- /dev/null +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/HostResolutionServiceImpl.java @@ -0,0 +1,178 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.springframework.stereotype.Component; + +import com.cloud.dc.dao.ClusterDao; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ResourceState; +import com.cloud.storage.DataStoreRole; +import com.cloud.utils.exception.CloudRuntimeException; +import com.google.common.base.Preconditions; + +/** + * Eligible-host lookups for storage-side data-motion operations - + * extracted from {@link StorageSystemDataMotionStrategy}. + * + * @see HostResolutionService + */ +@Component +public class HostResolutionServiceImpl implements HostResolutionService { + + private static final Random RANDOM = new Random(System.nanoTime()); + + @Inject + private ClusterDao clusterDao; + + @Inject + private HostDao hostDao; + + @Inject + private DataStoreManager dataStoreMgr; + + @Inject + private ResourceManager resourceManager; + + @Override + public HostVO getHost(SnapshotInfo snapshotInfo) { + HypervisorType hypervisorType = snapshotInfo.getHypervisorType(); + + if (HypervisorType.XenServer.equals(hypervisorType)) { + HostVO hostVO = getHost(snapshotInfo, hypervisorType, true); + + if (hostVO == null) { + hostVO = getHost(snapshotInfo, hypervisorType, false); + + if (hostVO == null) { + throw new CloudRuntimeException("Unable to locate an applicable host in data center with ID = " + snapshotInfo.getDataCenterId()); + } + } + + return hostVO; + } + + if (HypervisorType.VMware.equals(hypervisorType) || HypervisorType.KVM.equals(hypervisorType)) { + return getHost(snapshotInfo, hypervisorType, false); + } + + throw new CloudRuntimeException("Unsupported hypervisor type"); + } + + @Override + public HostVO getHostInCluster(StoragePoolVO storagePool) { + DataStore store = dataStoreMgr.getDataStore(storagePool.getId(), DataStoreRole.Primary); + List hosts = resourceManager.getEligibleUpAndEnabledHostsInClusterForStorageConnection((PrimaryDataStoreInfo) store); + + if (hosts != null && hosts.size() > 0) { + Collections.shuffle(hosts, RANDOM); + + for (HostVO host : hosts) { + if (ResourceState.Enabled.equals(host.getResourceState())) { + return host; + } + } + } + + throw new CloudRuntimeException("Unable to locate a host"); + } + + @Override + public HostVO getHost(SnapshotInfo snapshotInfo, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) { + Long zoneId = snapshotInfo.getDataCenterId(); + Preconditions.checkArgument(zoneId != null, "Zone ID cannot be null."); + Preconditions.checkArgument(hypervisorType != null, "Hypervisor type cannot be null."); + + List hosts; + if (DataStoreRole.Primary.equals(snapshotInfo.getDataStore().getRole())) { + hosts = resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection(snapshotInfo.getDataStore(), zoneId, hypervisorType); + } else { + hosts = hostDao.listByDataCenterIdAndHypervisorType(zoneId, hypervisorType); + } + + return getHost(hosts, computeClusterMustSupportResign); + } + + @Override + public HostVO getHost(VolumeInfo volumeInfo, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) { + Long zoneId = volumeInfo.getDataCenterId(); + Preconditions.checkArgument(zoneId != null, "Zone ID cannot be null."); + Preconditions.checkArgument(hypervisorType != null, "Hypervisor type cannot be null."); + + List hosts; + if (DataStoreRole.Primary.equals(volumeInfo.getDataStore().getRole())) { + hosts = resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection(volumeInfo.getDataStore(), zoneId, hypervisorType); + } else { + hosts = hostDao.listByDataCenterIdAndHypervisorType(zoneId, hypervisorType); + } + + return getHost(hosts, computeClusterMustSupportResign); + } + + @Override + public HostVO getHost(List hosts, boolean computeClusterMustSupportResign) { + if (hosts == null) { + return null; + } + + List clustersToSkip = new ArrayList<>(); + + Collections.shuffle(hosts, RANDOM); + + for (HostVO host : hosts) { + if (!ResourceState.Enabled.equals(host.getResourceState())) { + continue; + } + + if (computeClusterMustSupportResign) { + long clusterId = host.getClusterId(); + + if (clustersToSkip.contains(clusterId)) { + continue; + } + + if (clusterDao.getSupportsResigning(clusterId)) { + return host; + } + else { + clustersToSkip.add(clusterId); + } + } + else { + return host; + } + } + + return null; + } +} diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmLiveStorageMigrationHandler.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmLiveStorageMigrationHandler.java new file mode 100644 index 000000000000..6e82fbb2ba77 --- /dev/null +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmLiveStorageMigrationHandler.java @@ -0,0 +1,321 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.command.CopyCmdAnswer; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckVirtualMachineAnswer; +import com.cloud.agent.api.CheckVirtualMachineCommand; +import com.cloud.agent.api.MigrateAnswer; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo; +import com.cloud.agent.api.PrepareForMigrationAnswer; +import com.cloud.agent.api.PrepareForMigrationCommand; +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.MigrationOptions; +import com.cloud.storage.Storage; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.StorageManager; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.GuestOSCategoryDao; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.dao.VMInstanceDao; + +@Component +public class KvmLiveStorageMigrationHandler { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected AgentManager agentManager; + @Inject + private GuestOSCategoryDao guestOsCategoryDao; + @Inject + private GuestOSDao guestOsDao; + @Inject + protected PrimaryDataStoreDao storagePoolDao; + @Inject + private VMInstanceDao vmDao; + @Inject + private VolumeDao volumeDao; + @Inject + private VolumeDataFactory volumeDataFactory; + @Inject + private VolumeService volumeService; + + public void handle(Map volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, + AsyncCompletionCallback callback, StorageSystemDataMotionStrategy context) { + String errMsg = null; + boolean success = false; + Map srcVolumeInfoToDestVolumeInfo = new HashMap<>(); + + try { + if (srcHost.getHypervisorType() != HypervisorType.KVM) { + throw new CloudRuntimeException("Invalid hypervisor type (only KVM supported for this operation at the time being)"); + } + + verifyLiveMigrationForKVM(volumeDataStoreMap); + + VMInstanceVO vmInstance = vmDao.findById(vmTO.getId()); + vmTO.setState(vmInstance.getState()); + List migrateDiskInfoList = new ArrayList<>(); + + Map migrateStorage = new HashMap<>(); + + boolean managedStorageDestination = false; + boolean migrateNonSharedInc = false; + for (Map.Entry entry : volumeDataStoreMap.entrySet()) { + VolumeInfo srcVolumeInfo = entry.getKey(); + DataStore destDataStore = entry.getValue(); + + VolumeVO srcVolume = volumeDao.findById(srcVolumeInfo.getId()); + StoragePoolVO destStoragePool = storagePoolDao.findById(destDataStore.getId()); + StoragePoolVO sourceStoragePool = storagePoolDao.findById(srcVolumeInfo.getPoolId()); + + if (sourceStoragePool.getId() == destStoragePool.getId() && sourceStoragePool.getPoolType() == Storage.StoragePoolType.PowerFlex) { + continue; + } + + if (!context.shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool)) { + continue; + } + + MigrationOptions.Type migrationType = context.decideMigrationTypeAndCopyTemplateIfNeeded(destHost, vmInstance, srcVolumeInfo, + sourceStoragePool, destStoragePool, destDataStore); + migrateNonSharedInc = migrateNonSharedInc || MigrationOptions.Type.LinkedClone.equals(migrationType); + + VolumeVO destVolume = context.duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool); + VolumeInfo destVolumeInfo = volumeDataFactory.getVolume(destVolume.getId(), destDataStore); + + destVolumeInfo.processEvent(Event.MigrationCopyRequested); + destVolumeInfo.processEvent(Event.MigrationCopySucceeded); + destVolumeInfo.processEvent(Event.MigrationRequested); + + context.setVolumeMigrationOptions(srcVolumeInfo, destVolumeInfo, vmTO, srcHost, destStoragePool, migrationType); + + destDataStore.getDriver().createAsync(destDataStore, destVolumeInfo, null); + + managedStorageDestination = destStoragePool.isManaged(); + String volumeIdentifier = managedStorageDestination ? destVolumeInfo.get_iScsiName() : destVolumeInfo.getUuid(); + + destVolume = volumeDao.findById(destVolume.getId()); + destVolume.setPath(volumeIdentifier); + + context.setVolumePath(destVolume); + + volumeDao.update(destVolume.getId(), destVolume); + + context.postVolumeCreationActions(srcVolumeInfo, destVolumeInfo); + + destVolumeInfo = volumeDataFactory.getVolume(destVolume.getId(), destDataStore); + + context.handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION); + + volumeService.grantAccess(destVolumeInfo, destHost, destDataStore); + + String destPath = context.generateDestPath(destHost, destStoragePool, destVolumeInfo); + + MigrateCommand.MigrateDiskInfo migrateDiskInfo; + + boolean isNonManagedToNfs = context.supportStoragePoolType(sourceStoragePool.getPoolType(), StoragePoolType.Filesystem) && + destStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem && !managedStorageDestination; + if (isNonManagedToNfs) { + migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), + MigrateCommand.MigrateDiskInfo.DiskType.FILE, + MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, + MigrateCommand.MigrateDiskInfo.Source.FILE, + context.connectHostToVolume(destHost, destVolumeInfo.getPoolId(), volumeIdentifier)); + } else { + String backingPath = context.generateBackingPath(destStoragePool, destVolumeInfo); + migrateDiskInfo = context.configureMigrateDiskInfo(srcVolumeInfo, destPath, backingPath); + migrateDiskInfo.setSourceDiskOnStorageFileSystem(context.isStoragePoolTypeOfFile(sourceStoragePool)); + migrateDiskInfoList.add(migrateDiskInfo); + } + context.prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath()); + + migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo); + + srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo); + } + + PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO); + Answer pfma; + + try { + pfma = agentManager.send(destHost.getId(), pfmc); + + if (pfma == null || !pfma.getResult()) { + String details = pfma != null ? pfma.getDetails() : "null answer returned"; + String msg = "Unable to prepare for migration due to the following: " + details; + + throw new AgentUnavailableException(msg, destHost.getId()); + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException("Operation timed out", destHost.getId()); + } + + VMInstanceVO vm = vmDao.findById(vmTO.getId()); + boolean isWindows = guestOsCategoryDao.findById(guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); + + MigrateCommand migrateCommand = new MigrateCommand(vmTO.getName(), destHost.getPrivateIpAddress(), isWindows, vmTO, true); + migrateCommand.setWait(StorageManager.KvmStorageOnlineMigrationWait.value()); + migrateCommand.setMigrateStorage(migrateStorage); + migrateCommand.setMigrateDiskInfoList(migrateDiskInfoList); + migrateCommand.setMigrateStorageManaged(managedStorageDestination); + migrateCommand.setMigrateNonSharedInc(migrateNonSharedInc); + + Integer newVmCpuShares = ((PrepareForMigrationAnswer) pfma).getNewVmCpuShares(); + if (newVmCpuShares != null) { + logger.debug(String.format("Setting CPU shares to [%d] as part of migrate VM with volumes command for VM [%s].", newVmCpuShares, vmTO)); + migrateCommand.setNewVmCpuShares(newVmCpuShares); + } + + boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value(); + migrateCommand.setAutoConvergence(kvmAutoConvergence); + + MigrateAnswer migrateAnswer = null; + try { + migrateAnswer = (MigrateAnswer)agentManager.send(srcHost.getId(), migrateCommand); + success = migrateAnswer != null && migrateAnswer.getResult(); + } catch (OperationTimedoutException ex) { + if (HypervisorType.KVM.equals(vm.getHypervisorType())) { + final Answer answer = agentManager.send(destHost.getId(), new CheckVirtualMachineCommand(vm.getInstanceName())); + if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) { + final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer)answer; + if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) { + logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, destHost)); + success = true; + } + } + } + if (!success) { + throw ex; + } + } + + context.handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost); + + if (!success) { + if (migrateAnswer == null) { + throw new CloudRuntimeException("Unable to get an answer to the migrate command"); + } + + if (!migrateAnswer.getResult()) { + errMsg = migrateAnswer.getDetails(); + + throw new CloudRuntimeException(errMsg); + } + } + } catch (AgentUnavailableException | OperationTimedoutException | CloudRuntimeException ex) { + String volumesAndStorages = volumeDataStoreMap.entrySet().stream() + .map(entry -> formatEntryOfVolumesAndStoragesAsJsonToDisplayOnLog(entry)).collect(Collectors.joining(",")); + + errMsg = String.format("Copy volume(s) to storage(s) [%s] and VM to host [%s] failed in StorageSystemDataMotionStrategy.copyAsync. Error message: [%s].", + volumesAndStorages, formatMigrationElementsAsJsonToDisplayOnLog("vm", vmTO.getId(), srcHost.getId(), destHost.getId()), ex.getMessage()); + logger.error(errMsg, ex); + + throw new CloudRuntimeException(errMsg); + } finally { + if (!success && !srcVolumeInfoToDestVolumeInfo.isEmpty()) { + for (VolumeInfo destVolumeInfo : srcVolumeInfoToDestVolumeInfo.values()) { + logger.info(String.format("Expunging dest volume [id: %s, state: %s] as part of failed VM migration with volumes command for VM [%s].", + destVolumeInfo.getId(), destVolumeInfo.getState(), vmTO.getId())); + destVolumeInfo.processEvent(Event.OperationFailed); + destVolumeInfo.processEvent(Event.DestroyRequested); + volumeService.expungeVolumeAsync(destVolumeInfo); + } + } + + CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg); + CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer); + result.setResult(errMsg); + callback.complete(result); + } + } + + protected void verifyLiveMigrationForKVM(Map volumeDataStoreMap) { + Boolean storageTypeConsistency = null; + for (Map.Entry entry : volumeDataStoreMap.entrySet()) { + VolumeInfo volumeInfo = entry.getKey(); + + Long storagePoolId = volumeInfo.getPoolId(); + StoragePoolVO srcStoragePoolVO = storagePoolDao.findById(storagePoolId); + + if (srcStoragePoolVO == null) { + throw new CloudRuntimeException("Volume with ID " + volumeInfo.getId() + " is not associated with a storage pool."); + } + + DataStore dataStore = entry.getValue(); + StoragePoolVO destStoragePoolVO = storagePoolDao.findById(dataStore.getId()); + + if (destStoragePoolVO == null) { + throw new CloudRuntimeException("Destination storage pool with ID " + dataStore.getId() + " was not located."); + } + + if (srcStoragePoolVO.isManaged() && srcStoragePoolVO.getId() != destStoragePoolVO.getId()) { + throw new CloudRuntimeException("Migrating a volume online with KVM from managed storage is not currently supported."); + } + + if (storageTypeConsistency == null) { + storageTypeConsistency = destStoragePoolVO.isManaged(); + } else if (storageTypeConsistency != destStoragePoolVO.isManaged()) { + throw new CloudRuntimeException("Destination storage pools must be either all managed or all not managed"); + } + } + } + + protected String formatMigrationElementsAsJsonToDisplayOnLog(String objectName, Object object, Object from, Object to) { + return String.format("{%s: \"%s\", from: \"%s\", to:\"%s\"}", objectName, object, from, to); + } + + protected String formatEntryOfVolumesAndStoragesAsJsonToDisplayOnLog(Map.Entry entry) { + VolumeInfo srcVolumeInfo = entry.getKey(); + DataStore destDataStore = entry.getValue(); + return formatMigrationElementsAsJsonToDisplayOnLog("volume", srcVolumeInfo.getId(), srcVolumeInfo.getPoolId(), destDataStore.getId()); + } +} diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonLiveStorageMigrationHandler.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonLiveStorageMigrationHandler.java new file mode 100644 index 000000000000..919d51661c4b --- /dev/null +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonLiveStorageMigrationHandler.java @@ -0,0 +1,287 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import java.util.HashMap; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.command.CopyCmdAnswer; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.storage.MigrateVolumeCommand; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.ScopeType; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.StorageManager; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.VolumeDetailVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; + +@Component +public class KvmNonLiveStorageMigrationHandler { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected AgentManager agentManager; + @Inject + private DataStoreManager dataStoreMgr; + @Inject + private HostDao hostDao; + @Inject + private PrimaryDataStoreDao storagePoolDao; + @Inject + private VolumeDao volumeDao; + @Inject + private VolumeDetailsDao volumeDetailsDao; + @Inject + private VolumeDataFactory volumeDataFactory; + @Inject + private VolumeService volumeService; + @Inject + protected HostResolutionService hostResolutionService; + + public void handleVolumeMigrationForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, AsyncCompletionCallback callback) { + VirtualMachine vm = srcVolumeInfo.getAttachedVM(); + + checkAvailableForMigration(vm); + + String errMsg = null; + HostVO hostVO = null; + try { + destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null); + VolumeVO volumeVO = volumeDao.findById(destVolumeInfo.getId()); + updatePathFromScsiName(volumeVO); + destVolumeInfo = volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); + hostVO = getHostOnWhichToExecuteMigrationCommand(srcVolumeInfo, destVolumeInfo); + + PrimaryDataStore pds = (PrimaryDataStore)dataStoreMgr.getPrimaryDataStore(destVolumeInfo.getDataStore().getUuid()); + if (pds == null) { + throw new CloudRuntimeException("Unable to find primary data store driver for this volume"); + } + + volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); + + destVolumeInfo = volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); + + String path = migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage"); + + updateVolumePath(destVolumeInfo.getId(), path); + volumeVO = volumeDao.findById(destVolumeInfo.getId()); + if (volumeVO.getFormat() == null) { + volumeVO.setFormat(ImageFormat.QCOW2); + volumeDao.update(volumeVO.getId(), volumeVO); + } + } catch (Exception ex) { + errMsg = "Primary storage migration failed due to an unexpected error: " + + ex.getMessage(); + if (ex instanceof CloudRuntimeException) { + throw ex; + } else { + throw new CloudRuntimeException(errMsg, ex); + } + } finally { + if (hostVO != null) { + try { + volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); + } catch (Exception e) { + logger.warn(String.format("Failed to revoke access for volume 'name=%s,uuid=%s' after a migration attempt", destVolumeInfo.getVolume(), destVolumeInfo.getUuid()), e); + } + } + + destVolumeInfo = volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); + + CopyCmdAnswer copyCmdAnswer; + if (errMsg != null) { + copyCmdAnswer = new CopyCmdAnswer(errMsg); + } + else { + destVolumeInfo = volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); + DataTO dataTO = destVolumeInfo.getTO(); + copyCmdAnswer = new CopyCmdAnswer(dataTO); + } + + CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer); + result.setResult(errMsg); + callback.complete(result); + } + } + + public void checkAvailableForMigration(VirtualMachine vm) { + if (vm != null && (vm.getState() != VirtualMachine.State.Stopped && vm.getState() != VirtualMachine.State.Migrating)) { + throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage on KVM is attached to " + + "a VM, the VM must be in the Stopped or Migrating state."); + } + } + + protected void updatePathFromScsiName(VolumeVO volumeVO) { + if (volumeVO.get_iScsiName() != null) { + volumeVO.setPath(volumeVO.get_iScsiName()); + volumeDao.update(volumeVO.getId(), volumeVO); + } + } + + protected HostVO getHostOnWhichToExecuteMigrationCommand(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) { + long srcStoragePoolId = srcVolumeInfo.getPoolId(); + StoragePoolVO srcStoragePoolVO = storagePoolDao.findById(srcStoragePoolId); + + HostVO hostVO; + + Scope srcScope = srcVolumeInfo.getDataStore().getScope(); + Scope destScope = destVolumeInfo.getDataStore().getScope(); + if (ScopeType.HOST.equals(srcScope.getScopeType())) { + hostVO = hostDao.findById(srcScope.getScopeId()); + } else if (ScopeType.HOST.equals(destScope.getScopeType())) { + hostVO = hostDao.findById(destScope.getScopeId()); + } else { + if (srcStoragePoolVO.getClusterId() != null) { + hostVO = hostResolutionService.getHostInCluster(srcStoragePoolVO); + } else { + hostVO = hostResolutionService.getHost(destVolumeInfo, HypervisorType.KVM, false); + } + } + + return hostVO; + } + + public String migrateVolumeForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) { + try { + Map srcDetails = getVolumeDetails(srcVolumeInfo); + Map destDetails = getVolumeDetails(destVolumeInfo); + + volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore()); + + MigrateVolumeCommand migrateVolumeCommand = new MigrateVolumeCommand(srcVolumeInfo.getTO(), destVolumeInfo.getTO(), + srcDetails, destDetails, StorageManager.KvmStorageOfflineMigrationWait.value()); + + volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore()); + handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION); + volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); + + MigrateVolumeAnswer migrateVolumeAnswer = (MigrateVolumeAnswer)agentManager.send(hostVO.getId(), migrateVolumeCommand); + if (migrateVolumeAnswer == null || !migrateVolumeAnswer.getResult()) { + if (migrateVolumeAnswer != null && StringUtils.isNotEmpty(migrateVolumeAnswer.getDetails())) { + throw new CloudRuntimeException(migrateVolumeAnswer.getDetails()); + } + else { + throw new CloudRuntimeException(errMsg); + } + } + return migrateVolumeAnswer.getVolumePath(); + } catch (CloudRuntimeException ex) { + throw ex; + } catch (Exception ex) { + throw new CloudRuntimeException("Unexpected error during volume migration: " + ex.getMessage(), ex); + } finally { + try { + volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore()); + volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); + handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION); + } catch (Throwable e) { + logger.warn("During cleanup post-migration and exception occured: " + e); + if (logger.isDebugEnabled()) { + logger.debug("Exception during post-migration cleanup.", e); + } + } + } + } + + protected void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState qualityOfServiceState) { + try { + ((PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver()).handleQualityOfServiceForVolumeMigration(volumeInfo, qualityOfServiceState); + } + catch (Exception ex) { + logger.warn(ex); + } + } + + protected Map getVolumeDetails(VolumeInfo volumeInfo) { + long storagePoolId = volumeInfo.getPoolId(); + StoragePoolVO storagePoolVO = storagePoolDao.findById(storagePoolId); + + if (!storagePoolVO.isManaged()) { + return null; + } + + Map volumeDetails = new HashMap<>(); + + VolumeVO volumeVO = volumeDao.findById(volumeInfo.getId()); + + volumeDetails.put(DiskTO.STORAGE_HOST, storagePoolVO.getHostAddress()); + volumeDetails.put(DiskTO.STORAGE_PORT, String.valueOf(storagePoolVO.getPort())); + volumeDetails.put(DiskTO.IQN, volumeVO.get_iScsiName()); + volumeDetails.put(DiskTO.PROTOCOL_TYPE, (volumeVO.getPoolType() != null) ? volumeVO.getPoolType().toString() : null); + volumeDetails.put(StorageManager.STORAGE_POOL_DISK_WAIT.toString(), String.valueOf(StorageManager.STORAGE_POOL_DISK_WAIT.valueIn(storagePoolVO.getId()))); + volumeDetails.put(DiskTO.VOLUME_SIZE, String.valueOf(volumeVO.getSize())); + volumeDetails.put(DiskTO.SCSI_NAA_DEVICE_ID, getVolumeProperty(volumeInfo.getId(), DiskTO.SCSI_NAA_DEVICE_ID)); + + ChapInfo chapInfo = volumeService.getChapInfo(volumeInfo, volumeInfo.getDataStore()); + + if (chapInfo != null) { + volumeDetails.put(DiskTO.CHAP_INITIATOR_USERNAME, chapInfo.getInitiatorUsername()); + volumeDetails.put(DiskTO.CHAP_INITIATOR_SECRET, chapInfo.getInitiatorSecret()); + volumeDetails.put(DiskTO.CHAP_TARGET_USERNAME, chapInfo.getTargetUsername()); + volumeDetails.put(DiskTO.CHAP_TARGET_SECRET, chapInfo.getTargetSecret()); + } + + return volumeDetails; + } + + protected String getVolumeProperty(long volumeId, String property) { + VolumeDetailVO volumeDetails = volumeDetailsDao.findDetail(volumeId, property); + + if (volumeDetails != null) { + return volumeDetails.getValue(); + } + + return null; + } + + public void updateVolumePath(long volumeId, String path) { + VolumeVO volumeVO = volumeDao.findById(volumeId); + + volumeVO.setPath(path); + + volumeDao.update(volumeId, volumeVO); + } +} diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java index 947b4af8f690..f2777e2ea2b5 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index bcade3a371c4..1eb829a3f438 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -20,21 +20,16 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; -import com.cloud.agent.api.CheckVirtualMachineAnswer; -import com.cloud.agent.api.CheckVirtualMachineCommand; -import com.cloud.agent.api.PrepareForMigrationAnswer; import com.cloud.resource.ResourceManager; import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; @@ -49,10 +44,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; import org.apache.cloudstack.engine.subsystem.api.storage.HostScope; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; -import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; import org.apache.cloudstack.engine.subsystem.api.storage.Scope; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StorageAction; @@ -82,9 +75,7 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; -import com.cloud.agent.api.MigrateAnswer; import com.cloud.agent.api.MigrateCommand; -import com.cloud.agent.api.MigrateCommand.MigrateDiskInfo; import com.cloud.agent.api.ModifyTargetsAnswer; import com.cloud.agent.api.ModifyTargetsCommand; import com.cloud.agent.api.PrepareForMigrationCommand; @@ -105,7 +96,6 @@ import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.resource.ResourceState; import com.cloud.storage.DataStoreRole; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.MigrationOptions; @@ -145,7 +135,6 @@ import com.google.common.base.Preconditions; import java.util.Arrays; import java.util.HashSet; -import java.util.stream.Collectors; import org.apache.commons.collections.CollectionUtils; import static org.apache.cloudstack.vm.UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME; @@ -153,7 +142,6 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { protected Logger logger = LogManager.getLogger(getClass()); - private static final Random RANDOM = new Random(System.nanoTime()); private static final int LOCK_TIME_IN_SECONDS = 300; private static final String OPERATION_NOT_SUPPORTED = "This operation is not supported."; @@ -206,6 +194,12 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { private VolumeDataFactory _volFactory; @Inject ResourceManager resourceManager; + @Inject + protected HostResolutionService hostResolutionService; + @Inject + protected KvmNonLiveStorageMigrationHandler kvmNonLiveStorageMigrationHandler; + @Inject + protected KvmLiveStorageMigrationHandler kvmLiveStorageMigrationHandler; @Override public StrategyPriority canHandle(DataObject srcData, DataObject destData) { @@ -427,7 +421,7 @@ private void handleCopyAsyncForVolumes(VolumeInfo srcVolumeInfo, VolumeInfo dest String errMsg = String.format("Currently migrating volumes between managed storage providers is not supported on %s hypervisor", srcVolumeInfo.getHypervisorType().toString()); handleError(errMsg, callback); } else { - handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback); + kvmNonLiveStorageMigrationHandler.handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback); } } else { handleVolumeMigrationFromNonManagedStorageToManagedStorage(srcVolumeInfo, destVolumeInfo, callback); @@ -538,7 +532,7 @@ private void handleVolumeMigrationFromManagedStorageToManagedStorage(VolumeInfo String errMsg = String.format("Currently migrating volumes between managed storage providers is not supported on %s hypervisor", srcVolumeInfo.getHypervisorType().toString()); handleError(errMsg, callback); } else { - handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback); + kvmNonLiveStorageMigrationHandler.handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback); } } @@ -555,7 +549,7 @@ private void handleVolumeMigrationFromManagedStorageToNonManagedStorage(VolumeIn HypervisorType hypervisorType = HypervisorType.KVM; VirtualMachine vm = srcVolumeInfo.getAttachedVM(); - checkAvailableForMigration(vm); + kvmNonLiveStorageMigrationHandler.checkAvailableForMigration(vm); long destStoragePoolId = destVolumeInfo.getPoolId(); StoragePoolVO destStoragePoolVO = _storagePoolDao.findById(destStoragePoolId); @@ -572,7 +566,7 @@ private void handleVolumeMigrationFromManagedStorageToNonManagedStorage(VolumeIn setCertainVolumeValuesNull(destVolumeInfo.getId()); // migrate the volume via the hypervisor - String path = migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from managed storage to non-managed storage"); + String path = kvmNonLiveStorageMigrationHandler.migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from managed storage to non-managed storage"); updateVolumePath(destVolumeInfo.getId(), path); } @@ -715,7 +709,7 @@ private void handleVolumeMigrationFromNonManagedStorageToManagedStorage(VolumeIn CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer); callback.complete(result); } else { - handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback); + kvmNonLiveStorageMigrationHandler.handleVolumeMigrationForKVM(srcVolumeInfo, destVolumeInfo, callback); } } catch (Exception ex) { @@ -837,119 +831,6 @@ private void handleFailedVolumeMigration(VolumeInfo srcVolumeInfo, VolumeInfo de _volumeDao.update(srcVolumeInfo.getId(), volumeVO); } - private void handleVolumeMigrationForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, AsyncCompletionCallback callback) { - VirtualMachine vm = srcVolumeInfo.getAttachedVM(); - - checkAvailableForMigration(vm); - - String errMsg = null; - HostVO hostVO = null; - try { - destVolumeInfo.getDataStore().getDriver().createAsync(destVolumeInfo.getDataStore(), destVolumeInfo, null); - VolumeVO volumeVO = _volumeDao.findById(destVolumeInfo.getId()); - updatePathFromScsiName(volumeVO); - destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); - hostVO = getHostOnWhichToExecuteMigrationCommand(srcVolumeInfo, destVolumeInfo); - - // if managed we need to grant access - PrimaryDataStore pds = (PrimaryDataStore)this.dataStoreMgr.getPrimaryDataStore(destVolumeInfo.getDataStore().getUuid()); - if (pds == null) { - throw new CloudRuntimeException("Unable to find primary data store driver for this volume"); - } - - // grant access (for managed volumes) - _volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); - - // re-retrieve volume to get any updated information from grant - destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); - - // migrate the volume via the hypervisor - String path = migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, hostVO, "Unable to migrate the volume from non-managed storage to managed storage"); - - updateVolumePath(destVolumeInfo.getId(), path); - volumeVO = _volumeDao.findById(destVolumeInfo.getId()); - // only set this if it was not set. default to QCOW2 for KVM - if (volumeVO.getFormat() == null) { - volumeVO.setFormat(ImageFormat.QCOW2); - _volumeDao.update(volumeVO.getId(), volumeVO); - } - } catch (Exception ex) { - errMsg = "Primary storage migration failed due to an unexpected error: " + - ex.getMessage(); - if (ex instanceof CloudRuntimeException) { - throw ex; - } else { - throw new CloudRuntimeException(errMsg, ex); - } - } finally { - // revoke access (for managed volumes) - if (hostVO != null) { - try { - _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); - } catch (Exception e) { - logger.warn(String.format("Failed to revoke access for volume 'name=%s,uuid=%s' after a migration attempt", destVolumeInfo.getVolume(), destVolumeInfo.getUuid()), e); - } - } - - // re-retrieve volume to get any updated information from grant - destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); - - CopyCmdAnswer copyCmdAnswer; - if (errMsg != null) { - copyCmdAnswer = new CopyCmdAnswer(errMsg); - } - else { - destVolumeInfo = _volumeDataFactory.getVolume(destVolumeInfo.getId(), destVolumeInfo.getDataStore()); - DataTO dataTO = destVolumeInfo.getTO(); - copyCmdAnswer = new CopyCmdAnswer(dataTO); - } - - CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer); - result.setResult(errMsg); - callback.complete(result); - } - } - - private void checkAvailableForMigration(VirtualMachine vm) { - if (vm != null && (vm.getState() != VirtualMachine.State.Stopped && vm.getState() != VirtualMachine.State.Migrating)) { - throw new CloudRuntimeException("Currently, if a volume to migrate from non-managed storage to managed storage on KVM is attached to " + - "a VM, the VM must be in the Stopped or Migrating state."); - } - } - - /** - * Only update the path from the iscsiName if the iscsiName is set. Otherwise take no action to avoid nullifying the path - * with a previously set path value. - */ - private void updatePathFromScsiName(VolumeVO volumeVO) { - if (volumeVO.get_iScsiName() != null) { - volumeVO.setPath(volumeVO.get_iScsiName()); - _volumeDao.update(volumeVO.getId(), volumeVO); - } - } - - private HostVO getHostOnWhichToExecuteMigrationCommand(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo) { - long srcStoragePoolId = srcVolumeInfo.getPoolId(); - StoragePoolVO srcStoragePoolVO = _storagePoolDao.findById(srcStoragePoolId); - - HostVO hostVO; - - // if either source or destination is a HOST-scoped storage pool, the migration MUST be performed on that host - if (ScopeType.HOST.equals(srcVolumeInfo.getDataStore().getScope().getScopeType())) { - hostVO = _hostDao.findById(srcVolumeInfo.getDataStore().getScope().getScopeId()); - } else if (ScopeType.HOST.equals(destVolumeInfo.getDataStore().getScope().getScopeType())) { - hostVO = _hostDao.findById(destVolumeInfo.getDataStore().getScope().getScopeId()); - } else { - if (srcStoragePoolVO.getClusterId() != null) { - hostVO = getHostInCluster(srcStoragePoolVO); - } else { - hostVO = getHost(destVolumeInfo, HypervisorType.KVM, false); - } - } - - return hostVO; - } - private VolumeInfo createTemporaryVolumeCopyOfSnapshotAdaptive(SnapshotInfo snapshotInfo) { VolumeInfo tempVolumeInfo = null; VolumeVO tempVolumeVO = null; @@ -1937,7 +1818,7 @@ private void deleteVolumeFromSnapshot(SnapshotInfo snapshotInfo) { } } - private void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState qualityOfServiceState) { + void handleQualityOfServiceForVolumeMigration(VolumeInfo volumeInfo, PrimaryDataStoreDriver.QualityOfServiceState qualityOfServiceState) { try { ((PrimaryDataStoreDriver)volumeInfo.getDataStore().getDriver()).handleQualityOfServiceForVolumeMigration(volumeInfo, qualityOfServiceState); } @@ -2020,198 +1901,10 @@ protected void setVolumeMigrationOptions(VolumeInfo srcVolumeInfo, VolumeInfo de */ @Override public void copyAsync(Map volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, AsyncCompletionCallback callback) { - String errMsg = null; - boolean success = false; - Map srcVolumeInfoToDestVolumeInfo = new HashMap<>(); - - try { - if (srcHost.getHypervisorType() != HypervisorType.KVM) { - throw new CloudRuntimeException("Invalid hypervisor type (only KVM supported for this operation at the time being)"); - } - - verifyLiveMigrationForKVM(volumeDataStoreMap); - - VMInstanceVO vmInstance = _vmDao.findById(vmTO.getId()); - vmTO.setState(vmInstance.getState()); - List migrateDiskInfoList = new ArrayList(); - - Map migrateStorage = new HashMap<>(); - - boolean managedStorageDestination = false; - boolean migrateNonSharedInc = false; - for (Map.Entry entry : volumeDataStoreMap.entrySet()) { - VolumeInfo srcVolumeInfo = entry.getKey(); - DataStore destDataStore = entry.getValue(); - - VolumeVO srcVolume = _volumeDao.findById(srcVolumeInfo.getId()); - StoragePoolVO destStoragePool = _storagePoolDao.findById(destDataStore.getId()); - StoragePoolVO sourceStoragePool = _storagePoolDao.findById(srcVolumeInfo.getPoolId()); - - // do not initiate migration for the same PowerFlex/ScaleIO pool - if (sourceStoragePool.getId() == destStoragePool.getId() && sourceStoragePool.getPoolType() == Storage.StoragePoolType.PowerFlex) { - continue; - } - - if (!shouldMigrateVolume(sourceStoragePool, destHost, destStoragePool)) { - continue; - } - - MigrationOptions.Type migrationType = decideMigrationTypeAndCopyTemplateIfNeeded(destHost, vmInstance, srcVolumeInfo, sourceStoragePool, destStoragePool, destDataStore); - migrateNonSharedInc = migrateNonSharedInc || MigrationOptions.Type.LinkedClone.equals(migrationType); - - VolumeVO destVolume = duplicateVolumeOnAnotherStorage(srcVolume, destStoragePool); - VolumeInfo destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore); - - // move the volume from Allocated to Creating - destVolumeInfo.processEvent(Event.MigrationCopyRequested); - // move the volume from Creating to Ready - destVolumeInfo.processEvent(Event.MigrationCopySucceeded); - // move the volume from Ready to Migrating - destVolumeInfo.processEvent(Event.MigrationRequested); - - setVolumeMigrationOptions(srcVolumeInfo, destVolumeInfo, vmTO, srcHost, destStoragePool, migrationType); - - // create a volume on the destination storage - destDataStore.getDriver().createAsync(destDataStore, destVolumeInfo, null); - - managedStorageDestination = destStoragePool.isManaged(); - String volumeIdentifier = managedStorageDestination ? destVolumeInfo.get_iScsiName() : destVolumeInfo.getUuid(); - - destVolume = _volumeDao.findById(destVolume.getId()); - destVolume.setPath(volumeIdentifier); - - setVolumePath(destVolume); - - _volumeDao.update(destVolume.getId(), destVolume); - - postVolumeCreationActions(srcVolumeInfo, destVolumeInfo); - - destVolumeInfo = _volumeDataFactory.getVolume(destVolume.getId(), destDataStore); - - handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION); - - _volumeService.grantAccess(destVolumeInfo, destHost, destDataStore); - - String destPath = generateDestPath(destHost, destStoragePool, destVolumeInfo); - - MigrateCommand.MigrateDiskInfo migrateDiskInfo; - - boolean isNonManagedToNfs = supportStoragePoolType(sourceStoragePool.getPoolType(), StoragePoolType.Filesystem) && destStoragePool.getPoolType() == StoragePoolType.NetworkFilesystem && !managedStorageDestination; - if (isNonManagedToNfs) { - migrateDiskInfo = new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), - MigrateCommand.MigrateDiskInfo.DiskType.FILE, - MigrateCommand.MigrateDiskInfo.DriverType.QCOW2, - MigrateCommand.MigrateDiskInfo.Source.FILE, - connectHostToVolume(destHost, destVolumeInfo.getPoolId(), volumeIdentifier)); - } else { - String backingPath = generateBackingPath(destStoragePool, destVolumeInfo); - migrateDiskInfo = configureMigrateDiskInfo(srcVolumeInfo, destPath, backingPath); - migrateDiskInfo.setSourceDiskOnStorageFileSystem(isStoragePoolTypeOfFile(sourceStoragePool)); - migrateDiskInfoList.add(migrateDiskInfo); - } - prepareDiskWithSecretConsumerDetail(vmTO, srcVolumeInfo, destVolumeInfo.getPath()); - - migrateStorage.put(srcVolumeInfo.getPath(), migrateDiskInfo); - - srcVolumeInfoToDestVolumeInfo.put(srcVolumeInfo, destVolumeInfo); - } - - PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO); - Answer pfma; - - try { - pfma = agentManager.send(destHost.getId(), pfmc); - - if (pfma == null || !pfma.getResult()) { - String details = pfma != null ? pfma.getDetails() : "null answer returned"; - String msg = "Unable to prepare for migration due to the following: " + details; - - throw new AgentUnavailableException(msg, destHost.getId()); - } - } catch (final OperationTimedoutException e) { - throw new AgentUnavailableException("Operation timed out", destHost.getId()); - } - - VMInstanceVO vm = _vmDao.findById(vmTO.getId()); - boolean isWindows = _guestOsCategoryDao.findById(_guestOsDao.findById(vm.getGuestOSId()).getCategoryId()).getName().equalsIgnoreCase("Windows"); - - MigrateCommand migrateCommand = new MigrateCommand(vmTO.getName(), destHost.getPrivateIpAddress(), isWindows, vmTO, true); - migrateCommand.setWait(StorageManager.KvmStorageOnlineMigrationWait.value()); - migrateCommand.setMigrateStorage(migrateStorage); - migrateCommand.setMigrateDiskInfoList(migrateDiskInfoList); - migrateCommand.setMigrateStorageManaged(managedStorageDestination); - migrateCommand.setMigrateNonSharedInc(migrateNonSharedInc); - - Integer newVmCpuShares = ((PrepareForMigrationAnswer) pfma).getNewVmCpuShares(); - if (newVmCpuShares != null) { - logger.debug(String.format("Setting CPU shares to [%d] as part of migrate VM with volumes command for VM [%s].", newVmCpuShares, vmTO)); - migrateCommand.setNewVmCpuShares(newVmCpuShares); - } - - boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value(); - migrateCommand.setAutoConvergence(kvmAutoConvergence); - - MigrateAnswer migrateAnswer = null; - try { - migrateAnswer = (MigrateAnswer)agentManager.send(srcHost.getId(), migrateCommand); - success = migrateAnswer != null && migrateAnswer.getResult(); - } catch (OperationTimedoutException ex) { - if (HypervisorType.KVM.equals(vm.getHypervisorType())) { - final Answer answer = agentManager.send(destHost.getId(), new CheckVirtualMachineCommand(vm.getInstanceName())); - if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) { - final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer)answer; - if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) { - logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, destHost)); - success = true; - } - } - } - if (!success) { - throw ex; - } - } - - handlePostMigration(success, srcVolumeInfoToDestVolumeInfo, vmTO, destHost); - - if (!success) { - if (migrateAnswer == null) { - throw new CloudRuntimeException("Unable to get an answer to the migrate command"); - } - - if (!migrateAnswer.getResult()) { - errMsg = migrateAnswer.getDetails(); - - throw new CloudRuntimeException(errMsg); - } - } - } catch (AgentUnavailableException | OperationTimedoutException | CloudRuntimeException ex) { - String volumesAndStorages = volumeDataStoreMap.entrySet().stream().map(entry -> formatEntryOfVolumesAndStoragesAsJsonToDisplayOnLog(entry)).collect(Collectors.joining(",")); - - errMsg = String.format("Copy volume(s) to storage(s) [%s] and VM to host [%s] failed in StorageSystemDataMotionStrategy.copyAsync. Error message: [%s].", volumesAndStorages, formatMigrationElementsAsJsonToDisplayOnLog("vm", vmTO.getId(), srcHost.getId(), destHost.getId()), ex.getMessage()); - logger.error(errMsg, ex); - - throw new CloudRuntimeException(errMsg); - } finally { - if (!success && !srcVolumeInfoToDestVolumeInfo.isEmpty()) { - for (VolumeInfo destVolumeInfo : srcVolumeInfoToDestVolumeInfo.values()) { - logger.info(String.format("Expunging dest volume [id: %s, state: %s] as part of failed VM migration with volumes command for VM [%s].", destVolumeInfo.getId(), destVolumeInfo.getState(), vmTO.getId())); - destVolumeInfo.processEvent(Event.OperationFailed); - destVolumeInfo.processEvent(Event.DestroyRequested); - _volumeService.expungeVolumeAsync(destVolumeInfo); - } - } - - CopyCmdAnswer copyCmdAnswer = new CopyCmdAnswer(errMsg); - - CopyCommandResult result = new CopyCommandResult(null, copyCmdAnswer); - - result.setResult(errMsg); - - callback.complete(result); - } + kvmLiveStorageMigrationHandler.handle(volumeDataStoreMap, vmTO, srcHost, destHost, callback, this); } - private MigrationOptions.Type decideMigrationTypeAndCopyTemplateIfNeeded(Host destHost, VMInstanceVO vmInstance, VolumeInfo srcVolumeInfo, StoragePoolVO sourceStoragePool, StoragePoolVO destStoragePool, DataStore destDataStore) { + MigrationOptions.Type decideMigrationTypeAndCopyTemplateIfNeeded(Host destHost, VMInstanceVO vmInstance, VolumeInfo srcVolumeInfo, StoragePoolVO sourceStoragePool, StoragePoolVO destStoragePool, DataStore destDataStore) { VMTemplateVO vmTemplate = _vmTemplateDao.findById(vmInstance.getTemplateId()); String srcVolumeBackingFile = getVolumeBackingFile(srcVolumeInfo); if (StringUtils.isNotBlank(srcVolumeBackingFile) && supportStoragePoolType(destStoragePool.getPoolType(), StoragePoolType.Filesystem) && @@ -2280,7 +1973,7 @@ protected String generateBackingPath(StoragePoolVO destStoragePool, VolumeInfo d } /** - * Configures a {@link MigrateDiskInfo} object with disk type of BLOCK, Driver type RAW and Source DEV + * Configures a {@link MigrateCommand.MigrateDiskInfo} object with disk type of BLOCK, Driver type RAW and Source DEV */ protected MigrateCommand.MigrateDiskInfo configureMigrateDiskInfo(VolumeInfo srcVolumeInfo, String destPath, String backingPath) { return new MigrateCommand.MigrateDiskInfo(srcVolumeInfo.getPath(), @@ -2320,7 +2013,7 @@ String getVolumeBackingFile(VolumeInfo srcVolumeInfo) { return null; } - private void handlePostMigration(boolean success, Map srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host destHost) { + void handlePostMigration(boolean success, Map srcVolumeInfoToDestVolumeInfo, VirtualMachineTO vmTO, Host destHost) { if (!success) { try { PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(vmTO); @@ -2424,7 +2117,7 @@ private Long getSuitableDiskOfferingForVolumeOnPool(VolumeVO volume, StoragePool return null; } - private VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePoolVO storagePoolVO) { + VolumeVO duplicateVolumeOnAnotherStorage(Volume volume, StoragePoolVO storagePoolVO) { Long lastPoolId = volume.getPoolId(); VolumeVO newVol = new VolumeVO(volume); @@ -2794,110 +2487,23 @@ private Map getSnapshotDetails(SnapshotInfo snapshotInfo) { } private HostVO getHost(SnapshotInfo snapshotInfo) { - HypervisorType hypervisorType = snapshotInfo.getHypervisorType(); - - if (HypervisorType.XenServer.equals(hypervisorType)) { - HostVO hostVO = getHost(snapshotInfo, hypervisorType, true); - - if (hostVO == null) { - hostVO = getHost(snapshotInfo, hypervisorType, false); - - if (hostVO == null) { - throw new CloudRuntimeException("Unable to locate an applicable host in data center with ID = " + snapshotInfo.getDataCenterId()); - } - } - - return hostVO; - } - - if (HypervisorType.VMware.equals(hypervisorType) || HypervisorType.KVM.equals(hypervisorType)) { - return getHost(snapshotInfo, hypervisorType, false); - } - - throw new CloudRuntimeException("Unsupported hypervisor type"); + return hostResolutionService.getHost(snapshotInfo); } private HostVO getHostInCluster(StoragePoolVO storagePool) { - DataStore store = dataStoreMgr.getDataStore(storagePool.getId(), DataStoreRole.Primary); - List hosts = resourceManager.getEligibleUpAndEnabledHostsInClusterForStorageConnection((PrimaryDataStoreInfo) store); - - if (hosts != null && hosts.size() > 0) { - Collections.shuffle(hosts, RANDOM); - - for (HostVO host : hosts) { - if (ResourceState.Enabled.equals(host.getResourceState())) { - return host; - } - } - } - - throw new CloudRuntimeException("Unable to locate a host"); + return hostResolutionService.getHostInCluster(storagePool); } private HostVO getHost(SnapshotInfo snapshotInfo, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) { - Long zoneId = snapshotInfo.getDataCenterId(); - Preconditions.checkArgument(zoneId != null, "Zone ID cannot be null."); - Preconditions.checkArgument(hypervisorType != null, "Hypervisor type cannot be null."); - - List hosts; - if (DataStoreRole.Primary.equals(snapshotInfo.getDataStore().getRole())) { - hosts = resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection(snapshotInfo.getDataStore(), zoneId, hypervisorType); - } else { - hosts = _hostDao.listByDataCenterIdAndHypervisorType(zoneId, hypervisorType); - } - - return getHost(hosts, computeClusterMustSupportResign); + return hostResolutionService.getHost(snapshotInfo, hypervisorType, computeClusterMustSupportResign); } private HostVO getHost(VolumeInfo volumeInfo, HypervisorType hypervisorType, boolean computeClusterMustSupportResign) { - Long zoneId = volumeInfo.getDataCenterId(); - Preconditions.checkArgument(zoneId != null, "Zone ID cannot be null."); - Preconditions.checkArgument(hypervisorType != null, "Hypervisor type cannot be null."); - - List hosts; - if (DataStoreRole.Primary.equals(volumeInfo.getDataStore().getRole())) { - hosts = resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection(volumeInfo.getDataStore(), zoneId, hypervisorType); - } else { - hosts = _hostDao.listByDataCenterIdAndHypervisorType(zoneId, hypervisorType); - } - - return getHost(hosts, computeClusterMustSupportResign); + return hostResolutionService.getHost(volumeInfo, hypervisorType, computeClusterMustSupportResign); } private HostVO getHost(List hosts, boolean computeClusterMustSupportResign) { - if (hosts == null) { - return null; - } - - List clustersToSkip = new ArrayList<>(); - - Collections.shuffle(hosts, RANDOM); - - for (HostVO host : hosts) { - if (!ResourceState.Enabled.equals(host.getResourceState())) { - continue; - } - - if (computeClusterMustSupportResign) { - long clusterId = host.getClusterId(); - - if (clustersToSkip.contains(clusterId)) { - continue; - } - - if (clusterDao.getSupportsResigning(clusterId)) { - return host; - } - else { - clustersToSkip.add(clusterId); - } - } - else { - return host; - } - } - - return null; + return hostResolutionService.getHost(hosts, computeClusterMustSupportResign); } private Map getDetails(DataObject dataObj) { @@ -3013,48 +2619,6 @@ private DataObject cacheSnapshotChain(SnapshotInfo snapshot, Scope scope) { return leafData; } - private String migrateVolumeForKVM(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) { - try { - Map srcDetails = getVolumeDetails(srcVolumeInfo); - Map destDetails = getVolumeDetails(destVolumeInfo); - - _volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore()); - - MigrateVolumeCommand migrateVolumeCommand = new MigrateVolumeCommand(srcVolumeInfo.getTO(), destVolumeInfo.getTO(), - srcDetails, destDetails, StorageManager.KvmStorageOfflineMigrationWait.value()); - - _volumeService.grantAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore()); - handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION); - _volumeService.grantAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); - - MigrateVolumeAnswer migrateVolumeAnswer = (MigrateVolumeAnswer)agentManager.send(hostVO.getId(), migrateVolumeCommand); - if (migrateVolumeAnswer == null || !migrateVolumeAnswer.getResult()) { - if (migrateVolumeAnswer != null && StringUtils.isNotEmpty(migrateVolumeAnswer.getDetails())) { - throw new CloudRuntimeException(migrateVolumeAnswer.getDetails()); - } - else { - throw new CloudRuntimeException(errMsg); - } - } - return migrateVolumeAnswer.getVolumePath(); - } catch (CloudRuntimeException ex) { - throw ex; - } catch (Exception ex) { - throw new CloudRuntimeException("Unexpected error during volume migration: " + ex.getMessage(), ex); - } finally { - try { - _volumeService.revokeAccess(srcVolumeInfo, hostVO, srcVolumeInfo.getDataStore()); - _volumeService.revokeAccess(destVolumeInfo, hostVO, destVolumeInfo.getDataStore()); - handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION); - } catch (Throwable e) { - logger.warn("During cleanup post-migration and exception occured: " + e); - if (logger.isDebugEnabled()) { - logger.debug("Exception during post-migration cleanup.", e); - } - } - } - } - private String copyManagedVolumeToSecondaryStorage(VolumeInfo srcVolumeInfo, VolumeInfo destVolumeInfo, HostVO hostVO, String errMsg) { boolean srcVolumeDetached = srcVolumeInfo.getAttachedVM() == null; diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/HostResolutionServiceImplTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/HostResolutionServiceImplTest.java new file mode 100644 index 000000000000..a6d7f08c23f7 --- /dev/null +++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/HostResolutionServiceImplTest.java @@ -0,0 +1,408 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.dc.dao.ClusterDao; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.resource.ResourceManager; +import com.cloud.resource.ResourceState; +import com.cloud.storage.DataStoreRole; +import com.cloud.utils.exception.CloudRuntimeException; + +/** + * Focused tests for {@link HostResolutionServiceImpl} -- the Phase 4 + * extraction of host-resolution helpers from + * {@link StorageSystemDataMotionStrategy}. + * + * Behavior here is also exercised indirectly through the strategy's + * delegating wrappers; these tests target the service directly so future + * refactors of the strategy cannot silently drop coverage. + */ +@RunWith(MockitoJUnitRunner.class) +public class HostResolutionServiceImplTest { + + @Mock + private ClusterDao clusterDao; + + @Mock + private HostDao hostDao; + + @Mock + private DataStoreManager dataStoreMgr; + + @Mock + private ResourceManager resourceManager; + + @InjectMocks + private HostResolutionServiceImpl service; + + private static final long ZONE_ID = 42L; + + private HostVO host(long id, long clusterId, ResourceState state) { + HostVO h = Mockito.mock(HostVO.class); + Mockito.lenient().when(h.getId()).thenReturn(id); + Mockito.lenient().when(h.getClusterId()).thenReturn(clusterId); + Mockito.lenient().when(h.getResourceState()).thenReturn(state); + return h; + } + + private SnapshotInfo snapshot(DataStoreRole storeRole, HypervisorType hyper) { + SnapshotInfo s = Mockito.mock(SnapshotInfo.class); + DataStore ds = Mockito.mock(DataStore.class); + Mockito.when(ds.getRole()).thenReturn(storeRole); + Mockito.when(s.getDataStore()).thenReturn(ds); + Mockito.when(s.getDataCenterId()).thenReturn(ZONE_ID); + Mockito.when(s.getHypervisorType()).thenReturn(hyper); + return s; + } + + private VolumeInfo volume(DataStoreRole storeRole) { + VolumeInfo v = Mockito.mock(VolumeInfo.class); + DataStore ds = Mockito.mock(DataStore.class); + Mockito.when(ds.getRole()).thenReturn(storeRole); + Mockito.when(v.getDataStore()).thenReturn(ds); + Mockito.when(v.getDataCenterId()).thenReturn(ZONE_ID); + return v; + } + + // --------------------------------------------------------------------- + // getHost(List, boolean) -- the core filter / shuffle helper + // --------------------------------------------------------------------- + + @Test + public void getHostFromListReturnsNullWhenInputIsNull() { + assertNull(service.getHost((List) null, false)); + } + + @Test + public void getHostFromListReturnsNullWhenAllHostsDisabled() { + List hosts = new ArrayList<>(Arrays.asList( + host(1L, 1L, ResourceState.Disabled), + host(2L, 1L, ResourceState.Maintenance))); + + assertNull(service.getHost(hosts, false)); + } + + @Test + public void getHostFromListReturnsSingleEnabledHostWithoutResignCheck() { + HostVO enabled = host(7L, 1L, ResourceState.Enabled); + List hosts = new ArrayList<>(Arrays.asList(enabled)); + + HostVO result = service.getHost(hosts, false); + + assertSame(enabled, result); + } + + @Test + public void getHostFromListSkipsDisabledHostsWithoutResignCheck() { + HostVO disabled = host(1L, 1L, ResourceState.Disabled); + HostVO enabled = host(2L, 1L, ResourceState.Enabled); + List hosts = new ArrayList<>(Arrays.asList(disabled, enabled)); + + HostVO result = service.getHost(hosts, false); + + assertSame(enabled, result); + } + + @Test + public void getHostFromListReturnsHostWhenClusterSupportsResign() { + HostVO enabled = host(5L, 99L, ResourceState.Enabled); + Mockito.when(clusterDao.getSupportsResigning(99L)).thenReturn(true); + + HostVO result = service.getHost(new ArrayList<>(Arrays.asList(enabled)), true); + + assertSame(enabled, result); + } + + @Test + public void getHostFromListReturnsNullWhenNoClusterSupportsResign() { + HostVO enabled = host(5L, 99L, ResourceState.Enabled); + Mockito.when(clusterDao.getSupportsResigning(99L)).thenReturn(false); + + assertNull(service.getHost(new ArrayList<>(Arrays.asList(enabled)), true)); + } + + @Test + public void getHostFromListChecksClusterOnlyOncePerCluster() { + // Two enabled hosts in the same non-resigning cluster -- cluster + // should only be queried once. + HostVO h1 = host(5L, 99L, ResourceState.Enabled); + HostVO h2 = host(6L, 99L, ResourceState.Enabled); + // No host in another cluster -- expected to return null after one cluster lookup. + Mockito.when(clusterDao.getSupportsResigning(99L)).thenReturn(false); + + assertNull(service.getHost(new ArrayList<>(Arrays.asList(h1, h2)), true)); + + Mockito.verify(clusterDao, Mockito.times(1)).getSupportsResigning(99L); + } + + @Test + public void getHostFromListPrefersResigningClusterWhenRequested() { + HostVO badCluster = host(1L, 10L, ResourceState.Enabled); + HostVO goodCluster = host(2L, 20L, ResourceState.Enabled); + Mockito.when(clusterDao.getSupportsResigning(10L)).thenReturn(false); + Mockito.when(clusterDao.getSupportsResigning(20L)).thenReturn(true); + + // Pin order by passing a fixed list -- shuffle is in-place and may + // visit either first, but the result must always be the resigning + // cluster's host. + for (int i = 0; i < 25; i++) { + HostVO result = service.getHost( + new ArrayList<>(Arrays.asList(badCluster, goodCluster)), true); + assertSame(goodCluster, result); + } + } + + // --------------------------------------------------------------------- + // getHost(SnapshotInfo, HypervisorType, boolean) + // --------------------------------------------------------------------- + + @Test + public void getHostForSnapshotUsesResourceManagerWhenStoreIsPrimary() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.KVM); + HostVO h = host(1L, 1L, ResourceState.Enabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + snap.getDataStore(), ZONE_ID, HypervisorType.KVM)) + .thenReturn(new ArrayList<>(Arrays.asList(h))); + + assertSame(h, service.getHost(snap, HypervisorType.KVM, false)); + Mockito.verify(hostDao, Mockito.never()) + .listByDataCenterIdAndHypervisorType(ArgumentMatchers.anyLong(), ArgumentMatchers.any()); + } + + @Test + public void getHostForSnapshotUsesHostDaoWhenStoreIsImage() { + SnapshotInfo snap = snapshot(DataStoreRole.Image, HypervisorType.VMware); + HostVO h = host(1L, 1L, ResourceState.Enabled); + Mockito.when(hostDao.listByDataCenterIdAndHypervisorType(ZONE_ID, HypervisorType.VMware)) + .thenReturn(new ArrayList<>(Arrays.asList(h))); + + assertSame(h, service.getHost(snap, HypervisorType.VMware, false)); + Mockito.verify(resourceManager, Mockito.never()) + .getEligibleUpAndEnabledHostsInZoneForStorageConnection( + ArgumentMatchers.any(), ArgumentMatchers.anyLong(), ArgumentMatchers.any()); + } + + @Test + public void getHostForSnapshotThrowsWhenZoneIdNull() { + SnapshotInfo snap = Mockito.mock(SnapshotInfo.class); + Mockito.when(snap.getDataCenterId()).thenReturn(null); + + assertThrows(IllegalArgumentException.class, + () -> service.getHost(snap, HypervisorType.KVM, false)); + } + + @Test + public void getHostForSnapshotThrowsWhenHypervisorTypeNull() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.KVM); + + assertThrows(IllegalArgumentException.class, + () -> service.getHost(snap, null, false)); + } + + // --------------------------------------------------------------------- + // getHost(VolumeInfo, HypervisorType, boolean) + // --------------------------------------------------------------------- + + @Test + public void getHostForVolumeUsesResourceManagerWhenStoreIsPrimary() { + VolumeInfo vol = volume(DataStoreRole.Primary); + HostVO h = host(3L, 7L, ResourceState.Enabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + vol.getDataStore(), ZONE_ID, HypervisorType.XenServer)) + .thenReturn(new ArrayList<>(Arrays.asList(h))); + + assertSame(h, service.getHost(vol, HypervisorType.XenServer, false)); + } + + @Test + public void getHostForVolumeUsesHostDaoWhenStoreIsImageCache() { + VolumeInfo vol = volume(DataStoreRole.ImageCache); + HostVO h = host(4L, 9L, ResourceState.Enabled); + Mockito.when(hostDao.listByDataCenterIdAndHypervisorType(ZONE_ID, HypervisorType.KVM)) + .thenReturn(new ArrayList<>(Arrays.asList(h))); + + assertSame(h, service.getHost(vol, HypervisorType.KVM, false)); + } + + // --------------------------------------------------------------------- + // getHost(SnapshotInfo) + // --------------------------------------------------------------------- + + @Test + public void getHostForSnapshotXenServerPrefersResigningCluster() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.XenServer); + HostVO resigning = host(1L, 100L, ResourceState.Enabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + snap.getDataStore(), ZONE_ID, HypervisorType.XenServer)) + .thenReturn(new ArrayList<>(Arrays.asList(resigning))); + Mockito.when(clusterDao.getSupportsResigning(100L)).thenReturn(true); + + assertSame(resigning, service.getHost(snap)); + } + + @Test + public void getHostForSnapshotXenServerFallsBackWhenNoResigningCluster() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.XenServer); + HostVO nonResigning = host(2L, 200L, ResourceState.Enabled); + + // First call (with resign=true) yields a host whose cluster does not + // support resigning -> getHost(...) returns null. Fallback (resign=false) + // succeeds. + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + ArgumentMatchers.eq(snap.getDataStore()), + ArgumentMatchers.eq(ZONE_ID), + ArgumentMatchers.eq(HypervisorType.XenServer))) + .thenReturn(new ArrayList<>(Arrays.asList(nonResigning)), + new ArrayList<>(Arrays.asList(nonResigning))); + Mockito.when(clusterDao.getSupportsResigning(200L)).thenReturn(false); + + assertSame(nonResigning, service.getHost(snap)); + } + + @Test + public void getHostForSnapshotXenServerThrowsWhenNoHostAvailable() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.XenServer); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + ArgumentMatchers.any(), ArgumentMatchers.anyLong(), ArgumentMatchers.eq(HypervisorType.XenServer))) + .thenReturn(Collections.emptyList()); + + CloudRuntimeException ex = assertThrows(CloudRuntimeException.class, + () -> service.getHost(snap)); + assertTrue(ex.getMessage().contains("Unable to locate an applicable host")); + } + + @Test + public void getHostForSnapshotKvmGoesDirectlyWithoutResignCheck() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.KVM); + HostVO h = host(8L, 3L, ResourceState.Enabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + snap.getDataStore(), ZONE_ID, HypervisorType.KVM)) + .thenReturn(new ArrayList<>(Arrays.asList(h))); + + assertSame(h, service.getHost(snap)); + // No cluster lookup for KVM path + Mockito.verifyNoInteractions(clusterDao); + } + + @Test + public void getHostForSnapshotVMwareGoesDirectlyWithoutResignCheck() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.VMware); + HostVO h = host(9L, 4L, ResourceState.Enabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInZoneForStorageConnection( + snap.getDataStore(), ZONE_ID, HypervisorType.VMware)) + .thenReturn(new ArrayList<>(Arrays.asList(h))); + + assertSame(h, service.getHost(snap)); + Mockito.verifyNoInteractions(clusterDao); + } + + @Test + public void getHostForSnapshotUnsupportedHypervisorThrows() { + SnapshotInfo snap = snapshot(DataStoreRole.Primary, HypervisorType.Hyperv); + + CloudRuntimeException ex = assertThrows(CloudRuntimeException.class, + () -> service.getHost(snap)); + assertEquals("Unsupported hypervisor type", ex.getMessage()); + } + + // --------------------------------------------------------------------- + // getHostInCluster(StoragePoolVO) + // --------------------------------------------------------------------- + + @Test + public void getHostInClusterReturnsFirstEnabledHost() { + StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool.getId()).thenReturn(11L); + + PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class); + Mockito.when(dataStoreMgr.getDataStore(11L, DataStoreRole.Primary)).thenReturn(store); + + HostVO disabled = host(1L, 1L, ResourceState.Disabled); + HostVO enabled = host(2L, 1L, ResourceState.Enabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInClusterForStorageConnection(store)) + .thenReturn(new ArrayList<>(Arrays.asList(disabled, enabled))); + + assertSame(enabled, service.getHostInCluster(pool)); + } + + @Test + public void getHostInClusterThrowsWhenNoHostsReturned() { + StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool.getId()).thenReturn(11L); + PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class); + Mockito.when(dataStoreMgr.getDataStore(11L, DataStoreRole.Primary)).thenReturn(store); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInClusterForStorageConnection(store)) + .thenReturn(Collections.emptyList()); + + CloudRuntimeException ex = assertThrows(CloudRuntimeException.class, + () -> service.getHostInCluster(pool)); + assertEquals("Unable to locate a host", ex.getMessage()); + } + + @Test + public void getHostInClusterThrowsWhenAllHostsDisabled() { + StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool.getId()).thenReturn(11L); + PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class); + Mockito.when(dataStoreMgr.getDataStore(11L, DataStoreRole.Primary)).thenReturn(store); + HostVO disabled = host(1L, 1L, ResourceState.Disabled); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInClusterForStorageConnection(store)) + .thenReturn(new ArrayList<>(Arrays.asList(disabled))); + + assertThrows(CloudRuntimeException.class, () -> service.getHostInCluster(pool)); + } + + @Test + public void getHostInClusterThrowsWhenResourceManagerReturnsNull() { + StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool.getId()).thenReturn(11L); + PrimaryDataStore store = Mockito.mock(PrimaryDataStore.class); + Mockito.when(dataStoreMgr.getDataStore(11L, DataStoreRole.Primary)).thenReturn(store); + Mockito.when(resourceManager.getEligibleUpAndEnabledHostsInClusterForStorageConnection(store)) + .thenReturn(null); + + assertThrows(CloudRuntimeException.class, () -> service.getHostInCluster(pool)); + } +} diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmLiveStorageMigrationHandlerTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmLiveStorageMigrationHandlerTest.java new file mode 100644 index 000000000000..d9d3fbb74ff3 --- /dev/null +++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmLiveStorageMigrationHandlerTest.java @@ -0,0 +1,229 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.host.Host; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class KvmLiveStorageMigrationHandlerTest { + + @Mock + private Host srcHost; + @Mock + private Host destHost; + @Mock + private VolumeInfo volumeInfo; + @Mock + private DataStore dataStore; + @Mock + private VirtualMachineTO vmTO; + @Mock + private AsyncCompletionCallback callback; + @Mock + private StorageSystemDataMotionStrategy context; + @Mock + private PrimaryDataStoreDao storagePoolDao; + + @InjectMocks + private KvmLiveStorageMigrationHandler handler; + + @Test + public void handleRejectsNonKvmSourceHostAndCompletesCallback() { + when(srcHost.getHypervisorType()).thenReturn(HypervisorType.XenServer); + when(vmTO.getId()).thenReturn(1L); + + try { + handler.handle(Collections.singletonMap(volumeInfo, dataStore), vmTO, srcHost, destHost, callback, context); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + verify(callback).complete(any(CopyCommandResult.class)); + } + } + + @Test + public void verifyLiveMigrationThrowsWhenSrcStoragePoolIsNull() { + when(volumeInfo.getPoolId()).thenReturn(1L); + when(storagePoolDao.findById(1L)).thenReturn(null); + + try { + handler.verifyLiveMigrationForKVM(Collections.singletonMap(volumeInfo, dataStore)); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("not associated with a storage pool")); + } + } + + @Test + public void verifyLiveMigrationThrowsWhenDestStoragePoolIsNull() { + StoragePoolVO srcPool = Mockito.mock(StoragePoolVO.class); + when(volumeInfo.getPoolId()).thenReturn(1L); + when(dataStore.getId()).thenReturn(2L); + when(storagePoolDao.findById(1L)).thenReturn(srcPool); + when(storagePoolDao.findById(2L)).thenReturn(null); + + try { + handler.verifyLiveMigrationForKVM(Collections.singletonMap(volumeInfo, dataStore)); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("Destination storage pool")); + } + } + + @Test + public void verifyLiveMigrationThrowsWhenMigratingFromManagedStorageToDifferentPool() { + StoragePoolVO srcPool = Mockito.mock(StoragePoolVO.class); + StoragePoolVO destPool = Mockito.mock(StoragePoolVO.class); + when(volumeInfo.getPoolId()).thenReturn(1L); + when(dataStore.getId()).thenReturn(2L); + when(storagePoolDao.findById(1L)).thenReturn(srcPool); + when(storagePoolDao.findById(2L)).thenReturn(destPool); + when(srcPool.isManaged()).thenReturn(true); + when(srcPool.getId()).thenReturn(1L); + when(destPool.getId()).thenReturn(2L); + + try { + handler.verifyLiveMigrationForKVM(Collections.singletonMap(volumeInfo, dataStore)); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("Migrating a volume online with KVM from managed storage")); + } + } + + @Test + public void verifyLiveMigrationThrowsWhenDestinationPoolTypesAreInconsistent() { + VolumeInfo volumeInfo2 = Mockito.mock(VolumeInfo.class); + DataStore dataStore2 = Mockito.mock(DataStore.class); + StoragePoolVO srcPool1 = Mockito.mock(StoragePoolVO.class); + StoragePoolVO destPool1 = Mockito.mock(StoragePoolVO.class); + StoragePoolVO srcPool2 = Mockito.mock(StoragePoolVO.class); + StoragePoolVO destPool2 = Mockito.mock(StoragePoolVO.class); + + when(volumeInfo.getPoolId()).thenReturn(1L); + when(dataStore.getId()).thenReturn(10L); + when(volumeInfo2.getPoolId()).thenReturn(2L); + when(dataStore2.getId()).thenReturn(20L); + when(storagePoolDao.findById(1L)).thenReturn(srcPool1); + when(storagePoolDao.findById(10L)).thenReturn(destPool1); + when(storagePoolDao.findById(2L)).thenReturn(srcPool2); + when(storagePoolDao.findById(20L)).thenReturn(destPool2); + when(srcPool1.isManaged()).thenReturn(false); + when(destPool1.isManaged()).thenReturn(false); + when(srcPool2.isManaged()).thenReturn(false); + when(destPool2.isManaged()).thenReturn(true); + + Map map = new HashMap<>(); + map.put(volumeInfo, dataStore); + map.put(volumeInfo2, dataStore2); + + try { + handler.verifyLiveMigrationForKVM(map); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + assertTrue(e.getMessage().contains("either all managed or all not managed")); + } + } + + @Test + public void verifyLiveMigrationSucceedsForUnmanagedPools() { + StoragePoolVO srcPool = Mockito.mock(StoragePoolVO.class); + StoragePoolVO destPool = Mockito.mock(StoragePoolVO.class); + when(volumeInfo.getPoolId()).thenReturn(1L); + when(dataStore.getId()).thenReturn(2L); + when(storagePoolDao.findById(1L)).thenReturn(srcPool); + when(storagePoolDao.findById(2L)).thenReturn(destPool); + when(srcPool.isManaged()).thenReturn(false); + when(destPool.isManaged()).thenReturn(false); + + // should not throw + handler.verifyLiveMigrationForKVM(Collections.singletonMap(volumeInfo, dataStore)); + } + + @Test + public void formatMigrationElementsAsJsonFormatsCorrectly() { + String result = handler.formatMigrationElementsAsJsonToDisplayOnLog("volume", "vol1", "host1", "host2"); + assertEquals("{volume: \"vol1\", from: \"host1\", to:\"host2\"}", result); + } + + @Test + public void formatEntryOfVolumesAndStoragesFormatsCorrectly() { + when(volumeInfo.getId()).thenReturn(100L); + when(volumeInfo.getPoolId()).thenReturn(1L); + when(dataStore.getId()).thenReturn(2L); + Map.Entry entry = Collections.singletonMap(volumeInfo, dataStore).entrySet().iterator().next(); + + String result = handler.formatEntryOfVolumesAndStoragesAsJsonToDisplayOnLog(entry); + assertTrue(result.contains("100")); + assertTrue(result.contains("1")); + assertTrue(result.contains("2")); + } + + @Test + public void handleRejectsVmwareHostAndCompletesCallback() { + when(srcHost.getHypervisorType()).thenReturn(HypervisorType.VMware); + when(vmTO.getId()).thenReturn(2L); + + try { + handler.handle(Collections.singletonMap(volumeInfo, dataStore), vmTO, srcHost, destHost, callback, context); + fail("Expected CloudRuntimeException"); + } catch (CloudRuntimeException e) { + verify(callback).complete(any(CopyCommandResult.class)); + } + } + + @Test + public void verifyLiveMigrationSucceedsForManagedSrcAndSameDestPool() { + StoragePoolVO srcPool = Mockito.mock(StoragePoolVO.class); + StoragePoolVO destPool = Mockito.mock(StoragePoolVO.class); + when(volumeInfo.getPoolId()).thenReturn(1L); + when(dataStore.getId()).thenReturn(1L); + when(storagePoolDao.findById(1L)).thenReturn(srcPool).thenReturn(destPool); + when(srcPool.isManaged()).thenReturn(true); + when(srcPool.getId()).thenReturn(1L); + when(destPool.getId()).thenReturn(1L); + when(destPool.isManaged()).thenReturn(true); + + // should not throw when same pool (srcId == destId) + handler.verifyLiveMigrationForKVM(Collections.singletonMap(volumeInfo, dataStore)); + } +} diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonLiveStorageMigrationHandlerTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonLiveStorageMigrationHandlerTest.java new file mode 100644 index 000000000000..df99b56123af --- /dev/null +++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonLiveStorageMigrationHandlerTest.java @@ -0,0 +1,213 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.motion; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.storage.MigrateVolumeAnswer; +import com.cloud.agent.api.storage.MigrateVolumeCommand; +import com.cloud.host.HostVO; +import com.cloud.storage.Storage; +import com.cloud.storage.VolumeDetailVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VirtualMachine; + +@RunWith(MockitoJUnitRunner.class) +public class KvmNonLiveStorageMigrationHandlerTest { + + @Mock + private AgentManager agentManager; + @Mock + private PrimaryDataStoreDao storagePoolDao; + @Mock + private VolumeDao volumeDao; + @Mock + private VolumeDetailsDao volumeDetailsDao; + @Mock + private VolumeService volumeService; + @Mock + private VolumeInfo srcVolumeInfo; + @Mock + private VolumeInfo destVolumeInfo; + @Mock + private DataStore srcDataStore; + @Mock + private DataStore destDataStore; + @Mock + private PrimaryDataStoreDriver destDataStoreDriver; + @Mock + private VirtualMachine virtualMachine; + + @InjectMocks + private KvmNonLiveStorageMigrationHandler handler; + + @Test(expected = CloudRuntimeException.class) + public void checkAvailableForMigrationRejectsRunningVm() { + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Running); + + handler.checkAvailableForMigration(virtualMachine); + } + + @Test + public void migrateVolumeForKVMGrantsAccessSendsCommandAndCleansUp() throws Exception { + HostVO host = Mockito.mock(HostVO.class); + when(host.getId()).thenReturn(10L); + configureVolume(srcVolumeInfo, srcDataStore, 1L, 101L); + configureVolume(destVolumeInfo, destDataStore, 2L, 202L); + when(destDataStore.getDriver()).thenReturn(destDataStoreDriver); + + StoragePoolVO srcPool = Mockito.mock(StoragePoolVO.class); + StoragePoolVO destPool = Mockito.mock(StoragePoolVO.class); + when(storagePoolDao.findById(1L)).thenReturn(srcPool); + when(storagePoolDao.findById(2L)).thenReturn(destPool); + when(srcPool.isManaged()).thenReturn(false); + when(destPool.isManaged()).thenReturn(true); + when(destPool.getHostAddress()).thenReturn("10.1.1.20"); + when(destPool.getPort()).thenReturn(3260); + + VolumeVO destVolumeVO = Mockito.mock(VolumeVO.class); + when(destVolumeVO.getPoolType()).thenReturn(Storage.StoragePoolType.Iscsi); + when(destVolumeVO.get_iScsiName()).thenReturn("iqn.dest"); + when(destVolumeVO.getSize()).thenReturn(1024L); + when(volumeDao.findById(202L)).thenReturn(destVolumeVO); + when(agentManager.send(eq(10L), any(MigrateVolumeCommand.class))).thenReturn(new MigrateVolumeAnswer(null, true, null, "new-volume-path")); + + String path = handler.migrateVolumeForKVM(srcVolumeInfo, destVolumeInfo, host, "migration failed"); + + assertEquals("new-volume-path", path); + verify(volumeService, Mockito.times(2)).grantAccess(srcVolumeInfo, host, srcDataStore); + InOrder inOrder = inOrder(destDataStoreDriver, volumeService, agentManager); + inOrder.verify(destDataStoreDriver).handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.MIGRATION); + inOrder.verify(volumeService).grantAccess(destVolumeInfo, host, destDataStore); + inOrder.verify(agentManager).send(eq(10L), any(MigrateVolumeCommand.class)); + inOrder.verify(volumeService).revokeAccess(srcVolumeInfo, host, srcDataStore); + inOrder.verify(volumeService).revokeAccess(destVolumeInfo, host, destDataStore); + inOrder.verify(destDataStoreDriver).handleQualityOfServiceForVolumeMigration(destVolumeInfo, PrimaryDataStoreDriver.QualityOfServiceState.NO_MIGRATION); + } + + @Test + public void checkAvailableForMigrationAllowsNullVm() { + // should not throw + handler.checkAvailableForMigration(null); + } + + @Test + public void checkAvailableForMigrationAllowsStoppedVm() { + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Stopped); + // should not throw + handler.checkAvailableForMigration(virtualMachine); + } + + @Test + public void checkAvailableForMigrationAllowsMigratingVm() { + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Migrating); + // should not throw + handler.checkAvailableForMigration(virtualMachine); + } + + @Test(expected = CloudRuntimeException.class) + public void checkAvailableForMigrationRejectsStartingVm() { + when(virtualMachine.getState()).thenReturn(VirtualMachine.State.Starting); + handler.checkAvailableForMigration(virtualMachine); + } + + @Test + public void updateVolumePathSetsPathAndUpdatesDao() { + VolumeVO volumeVO = Mockito.mock(VolumeVO.class); + when(volumeDao.findById(101L)).thenReturn(volumeVO); + + handler.updateVolumePath(101L, "/new/path"); + + verify(volumeVO).setPath("/new/path"); + verify(volumeDao).update(101L, volumeVO); + } + + @Test + public void getVolumePropertyReturnsNullWhenDetailNotFound() { + when(volumeDetailsDao.findDetail(100L, "key")).thenReturn(null); + + String result = handler.getVolumeProperty(100L, "key"); + + assertNull(result); + } + + @Test + public void getVolumePropertyReturnsValueWhenDetailFound() { + VolumeDetailVO detail = Mockito.mock(VolumeDetailVO.class); + when(volumeDetailsDao.findDetail(100L, "key")).thenReturn(detail); + when(detail.getValue()).thenReturn("myvalue"); + + String result = handler.getVolumeProperty(100L, "key"); + + assertEquals("myvalue", result); + } + + @Test + public void updatePathFromScsiNameSetsPathWhenScsiNameIsPresent() { + VolumeVO volumeVO = Mockito.mock(VolumeVO.class); + when(volumeVO.get_iScsiName()).thenReturn("iqn.2024-01.test"); + when(volumeVO.getId()).thenReturn(200L); + + handler.updatePathFromScsiName(volumeVO); + + verify(volumeVO).setPath("iqn.2024-01.test"); + verify(volumeDao).update(200L, volumeVO); + } + + @Test + public void updatePathFromScsiNameSkipsUpdateWhenScsiNameIsNull() { + VolumeVO volumeVO = Mockito.mock(VolumeVO.class); + when(volumeVO.get_iScsiName()).thenReturn(null); + + handler.updatePathFromScsiName(volumeVO); + + verify(volumeDao, never()).update(eq(200L), any()); + } + + private void configureVolume(VolumeInfo volumeInfo, DataStore dataStore, long poolId, long volumeId) { + when(volumeInfo.getPoolId()).thenReturn(poolId); + when(volumeInfo.getId()).thenReturn(volumeId); + when(volumeInfo.getDataStore()).thenReturn(dataStore); + when(volumeInfo.getTO()).thenReturn(new VolumeObjectTO()); + } +} diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java index f739fecf9bf1..d15c4e377f0f 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/SecondaryStorageServiceImpl.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.VMTemplateStorageResourceAssoc; import com.cloud.storage.download.DownloadListener; diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateDataFactoryImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateDataFactoryImpl.java index 3e1504beb3ad..edaf562fe229 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateDataFactoryImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateDataFactoryImpl.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.direct.download.DirectDownloadManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java index e29e89cf431c..02e826445e6b 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java @@ -29,7 +29,7 @@ import java.util.Set; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.exception.StorageUnavailableException; import org.apache.cloudstack.context.CallContext; diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java index 0fedf746fa62..ed47763fd329 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java @@ -24,8 +24,8 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java index 14db5ea57710..9c5809deb4ee 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/ImageStoreImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.Upload; import org.apache.logging.log4j.Logger; diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/TemplateObject.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/TemplateObject.java index 5cb500f5e6cf..01d5f8c5e1ce 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/TemplateObject.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/store/TemplateObject.java @@ -21,7 +21,7 @@ import java.util.Date; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.cpu.CPU; import com.cloud.storage.StorageManager; diff --git a/engine/storage/integration-test/src/test/java/com/cloud/vm/snapshot/dao/VmSnapshotDaoTest.java b/engine/storage/integration-test/src/test/java/com/cloud/vm/snapshot/dao/VmSnapshotDaoTest.java index be8d04a8a0e8..7431e159cc7f 100644 --- a/engine/storage/integration-test/src/test/java/com/cloud/vm/snapshot/dao/VmSnapshotDaoTest.java +++ b/engine/storage/integration-test/src/test/java/com/cloud/vm/snapshot/dao/VmSnapshotDaoTest.java @@ -20,7 +20,7 @@ import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.storage.test.CloudStackTestNGBase; import org.junit.Assert; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java index 0d2ebf559e89..097b3f127b42 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/allocator/StorageAllocatorTest.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRUTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRUTest.java index 61a04c5307d3..353e82c17634 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRUTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/cache/manager/StorageCacheReplacementAlgorithmLRUTest.java @@ -22,7 +22,7 @@ import java.util.Date; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import junit.framework.Assert; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentManagerSimpleImpl.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentManagerSimpleImpl.java index 1d072985a667..f6912f50635d 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentManagerSimpleImpl.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentManagerSimpleImpl.java @@ -23,7 +23,7 @@ import java.util.HashMap; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentTest.java index 196132b5ee9e..8119befbd74b 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/DirectAgentTest.java @@ -20,7 +20,7 @@ import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.mockito.Mockito; import org.springframework.test.context.ContextConfiguration; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/EndpointSelectorTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/EndpointSelectorTest.java index 6256452e72aa..91134b4b3488 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/EndpointSelectorTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/EndpointSelectorTest.java @@ -26,7 +26,7 @@ import java.util.Set; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import junit.framework.Assert; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/MockRpcCallBack.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/MockRpcCallBack.java index 8b3de65b4cca..850ced192ce3 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/MockRpcCallBack.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/MockRpcCallBack.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.storage.test; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/S3TemplateTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/S3TemplateTest.java index d1a7743cf7c6..a5eca8faa482 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/S3TemplateTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/S3TemplateTest.java @@ -24,7 +24,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.mockito.Matchers; import org.mockito.Mockito; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTest.java index 605a5ffa8f09..cdf1ff45e916 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTest.java @@ -24,7 +24,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTestWithFakeData.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTestWithFakeData.java index 9868ccdf29a3..b05274315ddf 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTestWithFakeData.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/SnapshotTestWithFakeData.java @@ -34,7 +34,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/TemplateTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/TemplateTest.java index 0c42b1e6b6c3..8c0250e287fb 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/TemplateTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/TemplateTest.java @@ -21,7 +21,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.mockito.Matchers; import org.mockito.Mockito; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeServiceTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeServiceTest.java index 1e6a85ecff44..1cfdda0d252e 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeServiceTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeServiceTest.java @@ -27,7 +27,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTest.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTest.java index a2266020047c..cad07786fbaa 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTest.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTest.java @@ -24,7 +24,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTestVmware.java b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTestVmware.java index b2966d6f5c11..3932a4d8d17d 100644 --- a/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTestVmware.java +++ b/engine/storage/integration-test/src/test/java/org/apache/cloudstack/storage/test/VolumeTestVmware.java @@ -24,7 +24,7 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java index 222b21e0ce84..43d7227facb1 100644 --- a/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java +++ b/engine/storage/object/src/main/java/org/apache/cloudstack/storage/object/manager/ObjectStoreProviderManagerImpl.java @@ -31,8 +31,8 @@ import org.apache.cloudstack.storage.object.store.ObjectStoreImpl; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java index d9d028d4d085..edd4bcb7103f 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/CephSnapshotStrategy.java @@ -18,7 +18,7 @@ */ package org.apache.cloudstack.storage.snapshot; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java index 88f479c09045..ce248e430515 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/DefaultSnapshotStrategy.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Objects; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.VolumeApiServiceImpl; import com.cloud.utils.db.TransactionCallback; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java index c1e38fc92512..10fde2300749 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/ScaleIOSnapshotStrategy.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.storage.snapshot; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java index 5c0a613d82d6..0ed7cf8f8f1d 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotDataFactoryImpl.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.Snapshot; import com.cloud.storage.Volume; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java index 6a8bbd93ca4e..b90d075025b7 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotObject.java @@ -22,7 +22,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java index 95345bdf9e0e..b44fcb7cbeab 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.agent.api.ConvertSnapshotAnswer; import com.cloud.agent.api.ConvertSnapshotCommand; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java index 57f8938540b0..2d80b62692b6 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStateMachineManagerImpl.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.storage.snapshot; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.Snapshot; import com.cloud.storage.Snapshot.Event; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java index 2bfcbc107f75..8b71edfd7531 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotStrategyBase.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.storage.snapshot; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotService; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java index 560bb4b2fc12..2aa91efaeb2a 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/StorageSystemSnapshotStrategy.java @@ -25,7 +25,7 @@ import java.util.Random; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java index b71d6cf3afac..4870969f05d3 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/DefaultVMSnapshotStrategy.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.hypervisor.Hypervisor; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java index 003065e394f5..abc17cadc30b 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/KvmFileBasedStorageVmSnapshotStrategy.java @@ -64,7 +64,7 @@ import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.commons.collections.CollectionUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java index aced750bd320..e13fbd48232a 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.storage.StoragePool; diff --git a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java index 31b13fc279e3..2b26e7b7ed8b 100644 --- a/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java +++ b/engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/StorageVMSnapshotStrategy.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java index 7d5d3c786e87..720061b03e57 100644 --- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java +++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyKVMTest.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.dao.BackupOfferingDao; diff --git a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyTest.java b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyTest.java index a20b52fac2dd..e80542f014ca 100644 --- a/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyTest.java +++ b/engine/storage/snapshot/src/test/java/org/apache/cloudstack/storage/vmsnapshot/VMSnapshotStrategyTest.java @@ -23,7 +23,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.dao.SnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDetailsDao; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/LocalHostEndpoint.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/LocalHostEndpoint.java index 758bbe0c8c48..9c009a6235b2 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/LocalHostEndpoint.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/LocalHostEndpoint.java @@ -21,7 +21,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/RemoteHostEndPoint.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/RemoteHostEndPoint.java index bd4bce29b0a0..2357d97a1414 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/RemoteHostEndPoint.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/RemoteHostEndPoint.java @@ -21,7 +21,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; import org.apache.cloudstack.framework.async.AsyncCompletionCallback; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java index 4057f7a051bf..1f5d392ac9cb 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java @@ -57,7 +57,7 @@ import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import java.math.BigDecimal; import java.security.SecureRandom; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java index e76669656617..9146974066cd 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ClusterScopeStoragePoolAllocator.java @@ -30,7 +30,7 @@ import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import java.util.ArrayList; import java.util.Arrays; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java index 39c29bd2c6e6..b71461688555 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/GarbageCollectingStoragePoolAllocator.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java index b8dcfb0ba7bf..f6021ca9d763 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/LocalStoragePoolAllocator.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/UseLocalForRootAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/UseLocalForRootAllocator.java index 4b150b26dc49..48505c8b2c25 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/UseLocalForRootAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/UseLocalForRootAllocator.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java index bdf531e147b4..ebcf71560c9e 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/ZoneWideStoragePoolAllocator.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.utils.Pair; import org.springframework.stereotype.Component; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java index 3f925f08322c..613d9ea01e63 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataObjectManagerImpl.java @@ -18,7 +18,7 @@ */ package org.apache.cloudstack.storage.datastore; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java index 757623e3d044..baf3accbc742 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java @@ -20,7 +20,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java index d03be9c4d294..83f4db0a40c8 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/ObjectInDataStoreManagerImpl.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.storage.datastore; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java index 665dd81888f1..b940b20f69bf 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/provider/DataStoreProviderManagerImpl.java @@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.storage.object.ObjectStoreDriver; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreVO.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreVO.java index f1d932355b9a..013d78421a93 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreVO.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/db/ObjectInDataStoreVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java index 061d18dc3769..3c2094332431 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/endpoint/DefaultEndPointSelector.java @@ -28,7 +28,7 @@ import java.util.Iterator; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.dc.DedicatedResourceVO; import com.cloud.dc.dao.DedicatedResourceDao; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/HypervisorHelperImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/HypervisorHelperImpl.java index 10af5d55d619..7769a0eaef1c 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/HypervisorHelperImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/HypervisorHelperImpl.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java index ec76bbb62beb..f9523ed832fc 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/StorageStrategyFactoryImpl.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/VMSnapshotHelperImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/VMSnapshotHelperImpl.java index 55551772a08a..07c889a9b944 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/VMSnapshotHelperImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/helper/VMSnapshotHelperImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.uservm.UserVm; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java index 26b39e30776f..3df1a5cbb9b0 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/BaseImageStoreDriverImpl.java @@ -27,7 +27,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreHelper.java index 51edd62326dd..e2f2041c6db2 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreHelper.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java index aceab4506781..31adc5de5f47 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/TemplateDataStoreDaoImpl.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.commons.collections.CollectionUtils; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java index 9eae1fc0711c..1bc0516a8980 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/db/VolumeDataStoreDaoImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.db.Filter; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/deployasis/DeployAsIsHelperImpl.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/deployasis/DeployAsIsHelperImpl.java index b39ef1dd1163..bd6728b081db 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/deployasis/DeployAsIsHelperImpl.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/deployasis/DeployAsIsHelperImpl.java @@ -60,7 +60,7 @@ import org.apache.logging.log4j.LogManager; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.util.Collection; import java.util.HashMap; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ImageFormatHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ImageFormatHelper.java index 352d9a84eb6b..b84356224a5d 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ImageFormatHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/image/format/ImageFormatHelper.java @@ -20,7 +20,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.java index a2275576bbef..fc06c934c13f 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/object/datastore/ObjectStoreHelper.java @@ -26,7 +26,7 @@ import org.apache.cloudstack.storage.datastore.db.ObjectStoreVO; import org.springframework.stereotype.Component; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Iterator; import java.util.Map; import java.util.UUID; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java index d17dae132a04..734b26fc51ad 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/volume/datastore/PrimaryDataStoreHelper.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.dc.dao.ClusterDao; import org.apache.cloudstack.annotation.AnnotationService; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java index b6029c271489..32d16dc5433b 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/PrimaryDataStoreImpl.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java index 959d63ed2b51..11e8de4133fa 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/BasePrimaryDataStoreLifeCycleImpl.java @@ -20,7 +20,7 @@ import java.util.Arrays; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.ClusterScope; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java index 59ac995052f7..28c7dd6c9b22 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/manager/PrimaryDataStoreProviderManagerImpl.java @@ -21,8 +21,8 @@ import java.util.HashMap; import java.util.Map; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.springframework.stereotype.Component; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java index 7de9000782ec..25f56f98c212 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java @@ -57,7 +57,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; import java.util.Map; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeService.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeService.java new file mode 100644 index 000000000000..2f9e12bd542a --- /dev/null +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeService.java @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.volume; + +import java.util.function.BiFunction; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; + +import com.cloud.storage.Volume; +import com.cloud.user.Account; + +/** + * Operations on the secondary (image) store for volumes -- volume sync + * reconciliation against the DB and moving volume files between account + * folders when ownership changes. + * + *

Extracted from {@link VolumeServiceImpl} as a cohesive slice covering + * the secondary-storage side of the volume data plane.

+ */ +public interface SecondaryStorageVolumeService { + + /** + * Reconcile the volume_store_ref DB rows for the given image store + * against the actual install paths reported by the store, fixing up + * download state, queueing re-downloads, and deleting orphaned files. + * + * @param store image store to sync + * @param downloader callback used to (re)trigger a volume download + * on the image store; typically {@code + * volumeService::createVolumeAsync} so that + * existing spy verifications keep working. + */ + void handleVolumeSync(DataStore store, + BiFunction> downloader); + + /** + * Move a volume's install path on the image store from the source + * account folder to the destination account folder, then update the + * volume_store_ref install path on the DB. + */ + void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount); + + /** + * Build the canonical volume path on the image store for a given + * (accountId, volumeId) pair. + */ + String buildVolumePath(long accountId, long volumeId); +} diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeServiceImpl.java new file mode 100644 index 000000000000..fa68ebe36db6 --- /dev/null +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeServiceImpl.java @@ -0,0 +1,353 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.volume; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.function.BiFunction; + +import jakarta.inject.Inject; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.storage.command.DeleteCommand; +import org.apache.cloudstack.storage.command.MoveVolumeCommand; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.ListVolumeAnswer; +import com.cloud.agent.api.storage.ListVolumeCommand; +import com.cloud.alert.AlertManager; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.RegisterVolumePayload; +import com.cloud.storage.ScopeType; +import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; +import com.cloud.storage.Volume; +import com.cloud.storage.Volume.State; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.template.TemplateConstants; +import com.cloud.storage.template.TemplateProp; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.exception.CloudRuntimeException; + +/** + * Image-store side of the volume data plane: reconcile volume_store_ref + * with what's actually on the image store, and move volume files between + * account folders. + * + *

Extracted from {@link VolumeServiceImpl} as a cohesive engine-side + * slice covering operations that touch secondary storage rather than + * primary pools.

+ * + * @see SecondaryStorageVolumeService + */ +@Component +public class SecondaryStorageVolumeServiceImpl implements SecondaryStorageVolumeService { + + protected Logger logger = LogManager.getLogger(getClass()); + + @Inject + protected VolumeDataStoreDao volumeStoreDao; + @Inject + protected VolumeDao volDao; + @Inject + protected VolumeDataFactory volFactory; + @Inject + protected EndPointSelector epSelector; + @Inject + protected ResourceLimitService resourceLimitMgr; + @Inject + protected AccountManager accountMgr; + @Inject + protected AlertManager alertMgr; + + @Override + public void handleVolumeSync(DataStore store, + BiFunction> downloader) { + if (store == null) { + logger.warn("Huh? image store is null"); + return; + } + long storeId = store.getId(); + + // add lock to make template sync for a data store only be done once + String lockString = "volumesync.storeId:" + storeId; + GlobalLock syncLock = GlobalLock.getInternLock(lockString); + try { + if (syncLock.lock(3)) { + try { + Map volumeInfos = listVolume(store); + if (volumeInfos == null) { + return; + } + + // find all the db volumes including those with NULL url column to avoid accidentally deleting volumes on image store later. + List dbVolumes = volumeStoreDao.listByStoreId(storeId); + List toBeDownloaded = new ArrayList<>(dbVolumes); + for (VolumeDataStoreVO volumeStore : dbVolumes) { + VolumeVO volume = volDao.findById(volumeStore.getVolumeId()); + if (volume == null) { + logger.warn("Volume_store_ref table shows that volume {} is " + + "on image store {}, but the volume is not found in volumes " + + "table, potentially some bugs in deleteVolume, so we just " + + "treat this volume to be deleted and mark it as destroyed", + volumeStore.getVolumeId(), store); + volumeStore.setDestroyed(true); + volumeStoreDao.update(volumeStore.getId(), volumeStore); + continue; + } + // Exists then don't download + if (volumeInfos.containsKey(volume.getId())) { + TemplateProp volInfo = volumeInfos.remove(volume.getId()); + toBeDownloaded.remove(volumeStore); + logger.info("Volume Sync found {} already in the volume image store table", volume); + if (volumeStore.getDownloadState() != Status.DOWNLOADED) { + volumeStore.setErrorString(""); + } + if (volInfo.isCorrupted()) { + volumeStore.setDownloadState(Status.DOWNLOAD_ERROR); + String msg = String.format("Volume %s is corrupted on image store", volume); + volumeStore.setErrorString(msg); + logger.info(msg); + if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { + logger.info("Volume Sync found {} uploaded using SSVM on image store {} as corrupted, marking it as failed", volume, store); + volumeStoreDao.update(volumeStore.getId(), volumeStore); + // mark volume as failed, so that storage GC will clean it up + VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId()); + volObj.processEvent(Event.OperationFailed); + } else if (volumeStore.getDownloadUrl() == null) { + msg = String.format("Volume (%s) with install path %s is corrupted, please check in image store: %s", volume, volInfo.getInstallPath(), store); + logger.warn(msg); + } else { + logger.info("Removing volume_store_ref entry for corrupted volume {}", volume); + volumeStoreDao.remove(volumeStore.getId()); + toBeDownloaded.add(volumeStore); + } + } else { // Put them in right status + volumeStore.setDownloadPercent(100); + volumeStore.setDownloadState(Status.DOWNLOADED); + volumeStore.setState(ObjectInDataStoreStateMachine.State.Ready); + volumeStore.setInstallPath(volInfo.getInstallPath()); + volumeStore.setSize(volInfo.getSize()); + volumeStore.setPhysicalSize(volInfo.getPhysicalSize()); + volumeStore.setLastUpdated(new Date()); + volumeStoreDao.update(volumeStore.getId(), volumeStore); + + if (volume.getSize() == 0) { + // Set volume size in volumes table + volume.setSize(volInfo.getSize()); + volDao.update(volumeStore.getVolumeId(), volume); + } + + if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { + VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId()); + volObj.processEvent(Event.OperationSucceeded); + } + + if (volInfo.getSize() > 0) { + try { + resourceLimitMgr.checkResourceLimit(accountMgr.getAccount(volume.getAccountId()), com.cloud.configuration.Resource.ResourceType.secondary_storage, + volInfo.getSize() - volInfo.getPhysicalSize()); + } catch (ResourceAllocationException e) { + logger.warn(e.getMessage()); + alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED, volume.getDataCenterId(), volume.getPodId(), e.getMessage(), e.getMessage()); + } finally { + resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(), + com.cloud.configuration.Resource.ResourceType.secondary_storage.getOrdinal()); + } + } + } + continue; + } else if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { // failed uploads through SSVM + logger.info("Volume Sync did not find {} uploaded using SSVM on image store {}, marking it as failed", volume, store); + toBeDownloaded.remove(volumeStore); + volumeStore.setDownloadState(Status.DOWNLOAD_ERROR); + String msg = String.format("Volume %s is corrupted on image store", volume); + volumeStore.setErrorString(msg); + volumeStoreDao.update(volumeStore.getId(), volumeStore); + // mark volume as failed, so that storage GC will clean it up + VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId()); + volObj.processEvent(Event.OperationFailed); + continue; + } + // Volume is not on secondary but we should download. + if (volumeStore.getDownloadState() != Status.DOWNLOADED) { + logger.info("Volume Sync did not find {} ready on image store {}, will request download to start/resume shortly", volume, store); + } + } + + // Download volumes which haven't been downloaded yet. + if (toBeDownloaded.size() > 0) { + for (VolumeDataStoreVO volumeHost : toBeDownloaded) { + if (volumeHost.getDownloadUrl() == null) { // If url is null, skip downloading + logger.info("Skip downloading volume " + volumeHost.getVolumeId() + " since no download url is specified."); + continue; + } + + // if this is a region store, and there is already an DOWNLOADED entry there without install_path information, which + // means that this is a duplicate entry from migration of previous NFS to staging. + if (store.getScope().getScopeType() == ScopeType.REGION) { + if (volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED && volumeHost.getInstallPath() == null) { + logger.info("Skip sync volume for migration of previous NFS to object store"); + continue; + } + } + + // reset volume status back to Allocated + VolumeObject vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId()); + logger.debug("Volume {} needs to be downloaded to {}", vol, store); + + vol.processEvent(Event.OperationFailed); // reset back volume status + // remove leftover volume_store_ref entry since re-download will create it again + volumeStoreDao.remove(volumeHost.getId()); + // get an updated volumeVO + vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId()); + RegisterVolumePayload payload = new RegisterVolumePayload(volumeHost.getDownloadUrl(), volumeHost.getChecksum(), vol.getFormat().toString()); + vol.addPayload(payload); + downloader.apply(vol, store); + } + } + + // Delete volumes which are not present on DB. + for (Map.Entry entry : volumeInfos.entrySet()) { + TemplateProp tInfo = entry.getValue(); + + // we cannot directly call expungeVolumeAsync here to reuse delete logic since in this case db does not have this volume at all. + VolumeObjectTO tmplTO = new VolumeObjectTO(); + tmplTO.setDataStore(store.getTO()); + tmplTO.setPath(tInfo.getInstallPath()); + tmplTO.setId(tInfo.getId()); + DeleteCommand dtCommand = new DeleteCommand(tmplTO); + EndPoint ep = epSelector.select(store); + Answer answer = null; + if (ep == null) { + String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + logger.error(errMsg); + answer = new Answer(dtCommand, false, errMsg); + } else { + answer = ep.sendMessage(dtCommand); + } + if (answer == null || !answer.getResult()) { + logger.info("Failed to deleted volume at store: {}", store); + + } else { + String description = String.format("Deleted volume %s on secondary storage %s", tInfo.getTemplateName(), store); + logger.info(description); + } + } + } finally { + syncLock.unlock(); + } + } else { + logger.info("Couldn't get global lock on {}, another thread may be doing volume sync on data store {} now.", lockString, store); + } + } finally { + syncLock.releaseRef(); + } + } + + protected Map listVolume(DataStore store) { + ListVolumeCommand cmd = new ListVolumeCommand(store.getTO(), store.getUri()); + EndPoint ep = epSelector.select(store); + Answer answer = null; + if (ep == null) { + String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; + logger.error(errMsg); + answer = new Answer(cmd, false, errMsg); + } else { + answer = ep.sendMessage(cmd); + } + if (answer != null && answer.getResult()) { + ListVolumeAnswer tanswer = (ListVolumeAnswer)answer; + return tanswer.getTemplateInfo(); + } else { + if (logger.isDebugEnabled()) { + logger.debug("Can not list volumes for image store {}", store); + } + } + + return null; + } + + @Override + public void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount) { + VolumeDataStoreVO volumeStore = volumeStoreDao.findByVolume(volume.getId()); + + if (volumeStore == null) { + logger.debug(String.format("Volume [%s] is not present in the secondary storage. Therefore we do not need to move it in the secondary storage.", volume)); + return; + } + logger.debug("Volume [{}] is present in secondary storage. It will be necessary to move it from the source account's [{}] folder to the destination " + + "account's [{}] folder.", volume, sourceAccount, destAccount); + + VolumeInfo volumeInfo = volFactory.getVolume(volume.getId(), DataStoreRole.Image); + String datastoreUri = volumeInfo.getDataStore().getUri(); + Path srcPath = Paths.get(volumeInfo.getPath()); + String destPath = buildVolumePath(destAccount.getAccountId(), volume.getId()); + + EndPoint ssvm = epSelector.findSsvm(volume.getDataCenterId()); + + MoveVolumeCommand cmd = new MoveVolumeCommand(volume.getUuid(), volume.getName(), destPath, srcPath.getParent().toString(), datastoreUri); + + Answer answer = ssvm.sendMessage(cmd); + + if (!answer.getResult()) { + String msg = String.format("Unable to move volume [%s] from [%s] (source account's [%s] folder) to [%s] (destination account's [%s] folder) in the secondary storage, due " + + "to [%s].", + volume, srcPath.getParent(), sourceAccount, destPath, destAccount, answer.getDetails()); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + + logger.debug("Volume [{}] was moved from [{}] (source account's [{}] folder) to [{}] (destination account's [{}] folder) in the secondary storage.", + volume, srcPath.getParent(), sourceAccount, destPath, destAccount); + + volumeStore.setInstallPath(String.format("%s/%s", destPath, srcPath.getFileName().toString())); + if (!volumeStoreDao.update(volumeStore.getId(), volumeStore)) { + String msg = String.format("Unable to update volume [%s] install path in the DB.", volume); + logger.error(msg); + throw new CloudRuntimeException(msg); + } + } + + @Override + public String buildVolumePath(long accountId, long volumeId) { + return String.format("%s/%s/%s", TemplateConstants.DEFAULT_VOLUME_ROOT_DIR, accountId, volumeId); + } +} diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeDataFactoryImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeDataFactoryImpl.java index 5c2a774f8a2b..40c07fb4ae66 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeDataFactoryImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeDataFactoryImpl.java @@ -21,7 +21,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java index 43218b3f6a02..2a00c278612a 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeObject.java @@ -18,7 +18,7 @@ import java.util.Date; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.VsphereStoragePolicyVO; diff --git a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java index 8731e8791ddd..40de6c45c3b4 100644 --- a/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java +++ b/engine/storage/volume/src/main/java/org/apache/cloudstack/storage/volume/VolumeServiceImpl.java @@ -19,18 +19,15 @@ package org.apache.cloudstack.storage.volume; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.ExecutionException; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.annotation.AnnotationService; @@ -68,8 +65,6 @@ import org.apache.cloudstack.storage.RemoteHostEndPoint; import org.apache.cloudstack.storage.command.CommandResult; import org.apache.cloudstack.storage.command.CopyCmdAnswer; -import org.apache.cloudstack.storage.command.DeleteCommand; -import org.apache.cloudstack.storage.command.MoveVolumeCommand; import org.apache.cloudstack.storage.datastore.PrimaryDataStoreProviderManager; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; @@ -93,20 +88,16 @@ import com.cloud.agent.api.ModifyTargetsCommand; import com.cloud.agent.api.storage.CheckAndRepairVolumeAnswer; import com.cloud.agent.api.storage.CheckAndRepairVolumeCommand; -import com.cloud.agent.api.storage.ListVolumeAnswer; -import com.cloud.agent.api.storage.ListVolumeCommand; import com.cloud.agent.api.storage.ResizeVolumeCommand; import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; import com.cloud.configuration.Resource.ResourceType; import com.cloud.dc.dao.ClusterDao; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageAccessException; import com.cloud.host.Host; import com.cloud.host.HostVO; @@ -121,7 +112,6 @@ import com.cloud.storage.CheckAndRepairVolumePayload; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.DataStoreRole; -import com.cloud.storage.RegisterVolumePayload; import com.cloud.storage.ScopeType; import com.cloud.storage.Storage; import com.cloud.storage.Storage.StoragePoolType; @@ -144,10 +134,7 @@ import com.cloud.storage.resource.StorageProcessor; import com.cloud.storage.snapshot.SnapshotApiService; import com.cloud.storage.snapshot.SnapshotManager; -import com.cloud.storage.template.TemplateConstants; -import com.cloud.storage.template.TemplateProp; import com.cloud.user.Account; -import com.cloud.user.AccountManager; import com.cloud.user.ResourceLimitService; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; @@ -176,10 +163,6 @@ public class VolumeServiceImpl implements VolumeService { @Inject ResourceLimitService _resourceLimitMgr; @Inject - AccountManager _accountMgr; - @Inject - AlertManager _alertMgr; - @Inject ConfigurationDao configDao; @Inject VolumeDataStoreDao _volumeStoreDao; @@ -221,6 +204,8 @@ public class VolumeServiceImpl implements VolumeService { private PassphraseDao passphraseDao; @Inject protected DiskOfferingDao diskOfferingDao; + @Inject + protected SecondaryStorageVolumeService secondaryStorageVolumeService; public VolumeServiceImpl() { } @@ -646,25 +631,6 @@ public long getTemplatePoolId() { } - private TemplateInfo waitForTemplateDownloaded(PrimaryDataStore store, TemplateInfo template) { - int storagePoolMaxWaitSeconds = NumbersUtil.parseInt(configDao.getValue(Config.StoragePoolMaxWaitSeconds.key()), 3600); - int sleepTime = 120; - int tries = storagePoolMaxWaitSeconds / sleepTime; - while (tries > 0) { - TemplateInfo tmpl = store.getTemplate(template.getId(), null); - if (tmpl != null) { - return tmpl; - } - try { - Thread.sleep(sleepTime * 1000); - } catch (InterruptedException e) { - logger.debug("waiting for template download been interrupted: " + e); - } - tries--; - } - return null; - } - @DB protected void createBaseImageAsync(VolumeInfo volume, PrimaryDataStore dataStore, TemplateInfo template, AsyncCallFuture future) { String deployAsIsConfiguration = volume.getDeployAsIsConfiguration(); @@ -2592,210 +2558,7 @@ protected Void resizeVolumeCallback(AsyncCallbackDispatcher volumeInfos = listVolume(store); - if (volumeInfos == null) { - return; - } - - // find all the db volumes including those with NULL url column to avoid accidentally deleting volumes on image store later. - List dbVolumes = _volumeStoreDao.listByStoreId(storeId); - List toBeDownloaded = new ArrayList<>(dbVolumes); - for (VolumeDataStoreVO volumeStore : dbVolumes) { - VolumeVO volume = volDao.findById(volumeStore.getVolumeId()); - if (volume == null) { - logger.warn("Volume_store_ref table shows that volume {} is " + - "on image store {}, but the volume is not found in volumes " + - "table, potentially some bugs in deleteVolume, so we just " + - "treat this volume to be deleted and mark it as destroyed", - volumeStore.getVolumeId(), store); - volumeStore.setDestroyed(true); - _volumeStoreDao.update(volumeStore.getId(), volumeStore); - continue; - } - // Exists then don't download - if (volumeInfos.containsKey(volume.getId())) { - TemplateProp volInfo = volumeInfos.remove(volume.getId()); - toBeDownloaded.remove(volumeStore); - logger.info("Volume Sync found {} already in the volume image store table", volume); - if (volumeStore.getDownloadState() != Status.DOWNLOADED) { - volumeStore.setErrorString(""); - } - if (volInfo.isCorrupted()) { - volumeStore.setDownloadState(Status.DOWNLOAD_ERROR); - String msg = String.format("Volume %s is corrupted on image store", volume); - volumeStore.setErrorString(msg); - logger.info(msg); - if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { - logger.info("Volume Sync found {} uploaded using SSVM on image store {} as corrupted, marking it as failed", volume, store); - _volumeStoreDao.update(volumeStore.getId(), volumeStore); - // mark volume as failed, so that storage GC will clean it up - VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId()); - volObj.processEvent(Event.OperationFailed); - } else if (volumeStore.getDownloadUrl() == null) { - msg = String.format("Volume (%s) with install path %s is corrupted, please check in image store: %s", volume, volInfo.getInstallPath(), store); - logger.warn(msg); - } else { - logger.info("Removing volume_store_ref entry for corrupted volume {}", volume); - _volumeStoreDao.remove(volumeStore.getId()); - toBeDownloaded.add(volumeStore); - } - } else { // Put them in right status - volumeStore.setDownloadPercent(100); - volumeStore.setDownloadState(Status.DOWNLOADED); - volumeStore.setState(ObjectInDataStoreStateMachine.State.Ready); - volumeStore.setInstallPath(volInfo.getInstallPath()); - volumeStore.setSize(volInfo.getSize()); - volumeStore.setPhysicalSize(volInfo.getPhysicalSize()); - volumeStore.setLastUpdated(new Date()); - _volumeStoreDao.update(volumeStore.getId(), volumeStore); - - if (volume.getSize() == 0) { - // Set volume size in volumes table - volume.setSize(volInfo.getSize()); - volDao.update(volumeStore.getVolumeId(), volume); - } - - if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { - VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId()); - volObj.processEvent(Event.OperationSucceeded); - } - - if (volInfo.getSize() > 0) { - try { - _resourceLimitMgr.checkResourceLimit(_accountMgr.getAccount(volume.getAccountId()), com.cloud.configuration.Resource.ResourceType.secondary_storage, - volInfo.getSize() - volInfo.getPhysicalSize()); - } catch (ResourceAllocationException e) { - logger.warn(e.getMessage()); - _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED, volume.getDataCenterId(), volume.getPodId(), e.getMessage(), e.getMessage()); - } finally { - _resourceLimitMgr.recalculateResourceCount(volume.getAccountId(), volume.getDomainId(), - com.cloud.configuration.Resource.ResourceType.secondary_storage.getOrdinal()); - } - } - } - continue; - } else if (volume.getState() == State.NotUploaded || volume.getState() == State.UploadInProgress) { // failed uploads through SSVM - logger.info("Volume Sync did not find {} uploaded using SSVM on image store {}, marking it as failed", volume, store); - toBeDownloaded.remove(volumeStore); - volumeStore.setDownloadState(Status.DOWNLOAD_ERROR); - String msg = String.format("Volume %s is corrupted on image store", volume); - volumeStore.setErrorString(msg); - _volumeStoreDao.update(volumeStore.getId(), volumeStore); - // mark volume as failed, so that storage GC will clean it up - VolumeObject volObj = (VolumeObject)volFactory.getVolume(volume.getId()); - volObj.processEvent(Event.OperationFailed); - continue; - } - // Volume is not on secondary but we should download. - if (volumeStore.getDownloadState() != Status.DOWNLOADED) { - logger.info("Volume Sync did not find {} ready on image store {}, will request download to start/resume shortly", volume, store); - } - } - - // Download volumes which haven't been downloaded yet. - if (toBeDownloaded.size() > 0) { - for (VolumeDataStoreVO volumeHost : toBeDownloaded) { - if (volumeHost.getDownloadUrl() == null) { // If url is null, skip downloading - logger.info("Skip downloading volume " + volumeHost.getVolumeId() + " since no download url is specified."); - continue; - } - - // if this is a region store, and there is already an DOWNLOADED entry there without install_path information, which - // means that this is a duplicate entry from migration of previous NFS to staging. - if (store.getScope().getScopeType() == ScopeType.REGION) { - if (volumeHost.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED && volumeHost.getInstallPath() == null) { - logger.info("Skip sync volume for migration of previous NFS to object store"); - continue; - } - } - - // reset volume status back to Allocated - VolumeObject vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId()); - logger.debug("Volume {} needs to be downloaded to {}", vol, store); - - vol.processEvent(Event.OperationFailed); // reset back volume status - // remove leftover volume_store_ref entry since re-download will create it again - _volumeStoreDao.remove(volumeHost.getId()); - // get an updated volumeVO - vol = (VolumeObject)volFactory.getVolume(volumeHost.getVolumeId()); - RegisterVolumePayload payload = new RegisterVolumePayload(volumeHost.getDownloadUrl(), volumeHost.getChecksum(), vol.getFormat().toString()); - vol.addPayload(payload); - createVolumeAsync(vol, store); - } - } - - // Delete volumes which are not present on DB. - for (Map.Entry entry : volumeInfos.entrySet()) { - Long uniqueName = entry.getKey(); - TemplateProp tInfo = entry.getValue(); - - // we cannot directly call expungeVolumeAsync here to reuse delete logic since in this case db does not have this volume at all. - VolumeObjectTO tmplTO = new VolumeObjectTO(); - tmplTO.setDataStore(store.getTO()); - tmplTO.setPath(tInfo.getInstallPath()); - tmplTO.setId(tInfo.getId()); - DeleteCommand dtCommand = new DeleteCommand(tmplTO); - EndPoint ep = _epSelector.select(store); - Answer answer = null; - if (ep == null) { - String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; - logger.error(errMsg); - answer = new Answer(dtCommand, false, errMsg); - } else { - answer = ep.sendMessage(dtCommand); - } - if (answer == null || !answer.getResult()) { - logger.info("Failed to deleted volume at store: {}", store); - - } else { - String description = String.format("Deleted volume %s on secondary storage %s", tInfo.getTemplateName(), store); - logger.info(description); - } - } - } finally { - syncLock.unlock(); - } - } else { - logger.info("Couldn't get global lock on {}, another thread may be doing volume sync on data store {} now.", lockString, store); - } - } finally { - syncLock.releaseRef(); - } - } - - private Map listVolume(DataStore store) { - ListVolumeCommand cmd = new ListVolumeCommand(store.getTO(), store.getUri()); - EndPoint ep = _epSelector.select(store); - Answer answer = null; - if (ep == null) { - String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; - logger.error(errMsg); - answer = new Answer(cmd, false, errMsg); - } else { - answer = ep.sendMessage(cmd); - } - if (answer != null && answer.getResult()) { - ListVolumeAnswer tanswer = (ListVolumeAnswer)answer; - return tanswer.getTemplateInfo(); - } else { - if (logger.isDebugEnabled()) { - logger.debug("Can not list volumes for image store {}", store); - } - } - - return null; + secondaryStorageVolumeService.handleVolumeSync(store, this::createVolumeAsync); } @Override @@ -2928,46 +2691,10 @@ public void unmanageVolume(long volumeId) { @Override public void moveVolumeOnSecondaryStorageToAnotherAccount(Volume volume, Account sourceAccount, Account destAccount) { - VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId()); - - if (volumeStore == null) { - logger.debug(String.format("Volume [%s] is not present in the secondary storage. Therefore we do not need to move it in the secondary storage.", volume)); - return; - } - logger.debug("Volume [{}] is present in secondary storage. It will be necessary to move it from the source account's [{}] folder to the destination " - + "account's [{}] folder.", volume, sourceAccount, destAccount); - - VolumeInfo volumeInfo = volFactory.getVolume(volume.getId(), DataStoreRole.Image); - String datastoreUri = volumeInfo.getDataStore().getUri(); - Path srcPath = Paths.get(volumeInfo.getPath()); - String destPath = buildVolumePath(destAccount.getAccountId(), volume.getId()); - - EndPoint ssvm = _epSelector.findSsvm(volume.getDataCenterId()); - - MoveVolumeCommand cmd = new MoveVolumeCommand(volume.getUuid(), volume.getName(), destPath, srcPath.getParent().toString(), datastoreUri); - - Answer answer = ssvm.sendMessage(cmd); - - if (!answer.getResult()) { - String msg = String.format("Unable to move volume [%s] from [%s] (source account's [%s] folder) to [%s] (destination account's [%s] folder) in the secondary storage, due " - + "to [%s].", - volume, srcPath.getParent(), sourceAccount, destPath, destAccount, answer.getDetails()); - logger.error(msg); - throw new CloudRuntimeException(msg); - } - - logger.debug("Volume [{}] was moved from [{}] (source account's [{}] folder) to [{}] (destination account's [{}] folder) in the secondary storage.", - volume, srcPath.getParent(), sourceAccount, destPath, destAccount); - - volumeStore.setInstallPath(String.format("%s/%s", destPath, srcPath.getFileName().toString())); - if (!_volumeStoreDao.update(volumeStore.getId(), volumeStore)) { - String msg = String.format("Unable to update volume [%s] install path in the DB.", volume); - logger.error(msg); - throw new CloudRuntimeException(msg); - } + secondaryStorageVolumeService.moveVolumeOnSecondaryStorageToAnotherAccount(volume, sourceAccount, destAccount); } protected String buildVolumePath(long accountId, long volumeId) { - return String.format("%s/%s/%s", TemplateConstants.DEFAULT_VOLUME_ROOT_DIR, accountId, volumeId); + return secondaryStorageVolumeService.buildVolumePath(accountId, volumeId); } } diff --git a/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeServiceImplTest.java b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeServiceImplTest.java new file mode 100644 index 000000000000..62b66426a911 --- /dev/null +++ b/engine/storage/volume/src/test/java/org/apache/cloudstack/storage/volume/SecondaryStorageVolumeServiceImplTest.java @@ -0,0 +1,520 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.volume; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiFunction; + +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; +import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; +import org.apache.cloudstack.engine.subsystem.api.storage.Scope; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService.VolumeApiResult; +import org.apache.cloudstack.framework.async.AsyncCallFuture; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedStatic; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.storage.ListVolumeAnswer; +import com.cloud.alert.AlertManager; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.ScopeType; +import com.cloud.storage.VMTemplateStorageResourceAssoc.Status; +import com.cloud.storage.Volume; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.template.TemplateProp; +import com.cloud.user.Account; +import com.cloud.user.AccountManager; +import com.cloud.user.ResourceLimitService; +import com.cloud.utils.db.GlobalLock; +import com.cloud.utils.exception.CloudRuntimeException; + +@RunWith(MockitoJUnitRunner.class) +public class SecondaryStorageVolumeServiceImplTest { + + private SecondaryStorageVolumeServiceImpl service; + + @Mock + private VolumeDataStoreDao volumeStoreDao; + @Mock + private VolumeDao volDao; + @Mock + private VolumeDataFactory volFactory; + @Mock + private EndPointSelector epSelector; + @Mock + private ResourceLimitService resourceLimitMgr; + @Mock + private AccountManager accountMgr; + @Mock + private AlertManager alertMgr; + + @Mock + private DataStore store; + @Mock + private EndPoint endPoint; + @Mock + private Scope scope; + + @Before + public void setUp() { + service = new SecondaryStorageVolumeServiceImpl(); + service.volumeStoreDao = volumeStoreDao; + service.volDao = volDao; + service.volFactory = volFactory; + service.epSelector = epSelector; + service.resourceLimitMgr = resourceLimitMgr; + service.accountMgr = accountMgr; + service.alertMgr = alertMgr; + } + + private BiFunction> noopDownloader() { + return (v, s) -> new AsyncCallFuture<>(); + } + + /** + * Stub the global lock so its lock() returns true and unlock()/releaseRef() + * are no-ops. Otherwise GlobalLock tries to acquire a real DB lock. + */ + private MockedStatic stubGlobalLock() { + MockedStatic mocked = Mockito.mockStatic(GlobalLock.class); + GlobalLock fake = Mockito.mock(GlobalLock.class); + Mockito.when(fake.lock(Mockito.anyInt())).thenReturn(true); + mocked.when(() -> GlobalLock.getInternLock(Mockito.anyString())).thenReturn(fake); + return mocked; + } + + // --- buildVolumePath ----------------------------------------------- + + @Test + public void buildVolumePathConstructsCanonicalPath() { + String path = service.buildVolumePath(42L, 7L); + Assert.assertTrue("expected DEFAULT_VOLUME_ROOT_DIR prefix, was: " + path, + path.endsWith("/42/7") && path.contains("/")); + } + + @Test + public void buildVolumePathHandlesZeroIds() { + String path = service.buildVolumePath(0L, 0L); + Assert.assertTrue(path.endsWith("/0/0")); + } + + // --- handleVolumeSync null store ----------------------------------- + + @Test + public void handleVolumeSyncReturnsEarlyOnNullStore() { + // null store check happens before any GlobalLock interaction + service.handleVolumeSync(null, noopDownloader()); + Mockito.verifyNoInteractions(volumeStoreDao, volDao, volFactory, epSelector); + } + + // --- listVolume ----------------------------------------------------- + + @Test + public void listVolumeReturnsNullWhenEndpointIsNull() { + Mockito.when(store.getTO()).thenReturn(null); + Mockito.when(store.getUri()).thenReturn("nfs://x"); + Mockito.when(epSelector.select(store)).thenReturn(null); + + Map result = service.listVolume(store); + + Assert.assertNull(result); + } + + @Test + public void listVolumeReturnsNullOnFailedAnswer() { + Mockito.when(store.getTO()).thenReturn(null); + Mockito.when(store.getUri()).thenReturn("nfs://x"); + Mockito.when(epSelector.select(store)).thenReturn(endPoint); + Answer failed = Mockito.mock(Answer.class); + Mockito.when(failed.getResult()).thenReturn(false); + Mockito.when(endPoint.sendMessage(Mockito.any())).thenReturn(failed); + + Map result = service.listVolume(store); + + Assert.assertNull(result); + } + + @Test + public void listVolumeReturnsTemplateInfoOnSuccess() { + Mockito.when(store.getTO()).thenReturn(null); + Mockito.when(store.getUri()).thenReturn("nfs://x"); + Mockito.when(epSelector.select(store)).thenReturn(endPoint); + Map expected = new HashMap<>(); + expected.put(1L, new TemplateProp("t1", "/path/t1", false, false)); + ListVolumeAnswer answer = Mockito.mock(ListVolumeAnswer.class); + Mockito.when(answer.getResult()).thenReturn(true); + Mockito.when(answer.getTemplateInfo()).thenReturn(expected); + Mockito.when(endPoint.sendMessage(Mockito.any())).thenReturn(answer); + + Map result = service.listVolume(store); + + Assert.assertSame(expected, result); + } + + // --- handleVolumeSync orphaned-ref ----------------------------------- + + @Test + public void handleVolumeSyncMarksOrphanedDbRowsDestroyed() { + // image store returns empty list, DB has 1 row whose volume id is missing + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Mockito.doReturn(new HashMap<>()).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + + VolumeDataStoreVO orphan = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(orphan.getId()).thenReturn(101L); + Mockito.when(orphan.getVolumeId()).thenReturn(202L); + Mockito.when(volumeStoreDao.listByStoreId(11L)).thenReturn(Collections.singletonList(orphan)); + Mockito.when(volDao.findById(202L)).thenReturn(null); + + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, noopDownloader()); + } + + Mockito.verify(orphan).setDestroyed(true); + Mockito.verify(volumeStoreDao).update(101L, orphan); + } + + // --- handleVolumeSync skip-when-listVolume-null ---------------------- + + @Test + public void handleVolumeSyncSkipsBodyWhenListVolumeReturnsNull() { + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Mockito.doReturn(null).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, noopDownloader()); + } + + // never queried for DB volumes -- sync body skipped + Mockito.verify(volumeStoreDao, Mockito.never()).listByStoreId(Mockito.anyLong()); + } + + // --- handleVolumeSync triggers downloader for missing volume -------- + + @Test + public void handleVolumeSyncTriggersDownloaderForMissingDownloadable() { + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Mockito.doReturn(new HashMap()).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + Mockito.when(store.getScope()).thenReturn(scope); + Mockito.when(scope.getScopeType()).thenReturn(ScopeType.ZONE); + + VolumeDataStoreVO toDownload = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(toDownload.getId()).thenReturn(101L); + Mockito.when(toDownload.getVolumeId()).thenReturn(202L); + Mockito.when(toDownload.getDownloadUrl()).thenReturn("http://src/vol.qcow2"); + Mockito.when(toDownload.getDownloadState()).thenReturn(Status.NOT_DOWNLOADED); + Mockito.when(volumeStoreDao.listByStoreId(11L)).thenReturn(Collections.singletonList(toDownload)); + + VolumeVO vol = new VolumeVO("v", 1L, 1L, 1L, 1L, 1L, "folder", "path", null, 0L, Volume.Type.DATADISK); + vol.setState(Volume.State.Ready); + Mockito.when(volDao.findById(202L)).thenReturn(vol); + + VolumeObject volObj = Mockito.mock(VolumeObject.class); + Mockito.when(volObj.getFormat()).thenReturn(com.cloud.storage.Storage.ImageFormat.QCOW2); + Mockito.when(volFactory.getVolume(202L)).thenReturn(volObj); + + @SuppressWarnings("unchecked") + BiFunction> downloader = Mockito.mock(BiFunction.class); + AsyncCallFuture future = new AsyncCallFuture<>(); + Mockito.when(downloader.apply(Mockito.any(), Mockito.any())).thenReturn(future); + + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, downloader); + } + + Mockito.verify(downloader).apply(volObj, store); + Mockito.verify(volumeStoreDao).remove(101L); + } + + @Test + public void handleVolumeSyncSkipsDownloadWhenUrlNull() { + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Mockito.doReturn(new HashMap()).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + + VolumeDataStoreVO noUrl = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(noUrl.getVolumeId()).thenReturn(202L); + Mockito.when(noUrl.getDownloadUrl()).thenReturn(null); + Mockito.when(volumeStoreDao.listByStoreId(11L)).thenReturn(Collections.singletonList(noUrl)); + + VolumeVO vol = new VolumeVO("v", 1L, 1L, 1L, 1L, 1L, "folder", "path", null, 0L, Volume.Type.DATADISK); + vol.setState(Volume.State.Ready); + Mockito.when(volDao.findById(202L)).thenReturn(vol); + + @SuppressWarnings("unchecked") + BiFunction> downloader = Mockito.mock(BiFunction.class); + + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, downloader); + } + + Mockito.verifyNoInteractions(downloader); + } + + // --- handleVolumeSync deletes store-only entries -------------------- + + @Test + public void handleVolumeSyncDeletesOrphanedStoreEntries() { + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Map volsOnStore = new HashMap<>(); + TemplateProp prop = new TemplateProp("orphanedOnStore", "/path/orphan", false, false); + volsOnStore.put(999L, prop); + Mockito.doReturn(volsOnStore).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + Mockito.when(store.getTO()).thenReturn(null); + Mockito.when(volumeStoreDao.listByStoreId(11L)).thenReturn(Collections.emptyList()); + + Mockito.when(epSelector.select(store)).thenReturn(endPoint); + Answer okAnswer = Mockito.mock(Answer.class); + Mockito.when(okAnswer.getResult()).thenReturn(true); + Mockito.when(endPoint.sendMessage(Mockito.any())).thenReturn(okAnswer); + + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, noopDownloader()); + } + + Mockito.verify(endPoint).sendMessage(Mockito.any(org.apache.cloudstack.storage.command.DeleteCommand.class)); + } + + @Test + public void handleVolumeSyncToleratesNullEndpointDuringDelete() { + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Map volsOnStore = new HashMap<>(); + volsOnStore.put(999L, new TemplateProp("orphan", "/p", false, false)); + Mockito.doReturn(volsOnStore).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + Mockito.when(store.getTO()).thenReturn(null); + Mockito.when(volumeStoreDao.listByStoreId(11L)).thenReturn(Collections.emptyList()); + Mockito.when(epSelector.select(store)).thenReturn(null); + + // should not throw + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, noopDownloader()); + } + } + + // --- moveVolumeOnSecondaryStorageToAnotherAccount ------------------- + + @Test + public void moveVolumeOnSecondaryReturnsEarlyWhenNoStoreRow() { + Volume volume = Mockito.mock(Volume.class); + Mockito.when(volume.getId()).thenReturn(7L); + Account src = Mockito.mock(Account.class); + Account dst = Mockito.mock(Account.class); + Mockito.when(volumeStoreDao.findByVolume(7L)).thenReturn(null); + + service.moveVolumeOnSecondaryStorageToAnotherAccount(volume, src, dst); + + Mockito.verifyNoInteractions(volFactory, epSelector); + } + + @Test + public void moveVolumeOnSecondaryUpdatesInstallPathOnSuccess() { + Volume volume = Mockito.mock(Volume.class); + Mockito.when(volume.getId()).thenReturn(7L); + Mockito.when(volume.getUuid()).thenReturn("uuid-1"); + Mockito.when(volume.getName()).thenReturn("vol1"); + Mockito.when(volume.getDataCenterId()).thenReturn(3L); + Account src = Mockito.mock(Account.class); + Account dst = Mockito.mock(Account.class); + Mockito.when(dst.getAccountId()).thenReturn(99L); + + VolumeDataStoreVO storeVo = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(storeVo.getId()).thenReturn(101L); + Mockito.when(volumeStoreDao.findByVolume(7L)).thenReturn(storeVo); + Mockito.when(volumeStoreDao.update(101L, storeVo)).thenReturn(true); + + VolumeInfo info = Mockito.mock(VolumeInfo.class); + DataStore secStore = Mockito.mock(DataStore.class); + Mockito.when(info.getDataStore()).thenReturn(secStore); + Mockito.when(secStore.getUri()).thenReturn("nfs://store"); + Mockito.when(info.getPath()).thenReturn("/template/2/7/file.qcow2"); + Mockito.when(volFactory.getVolume(7L, DataStoreRole.Image)).thenReturn(info); + + EndPoint ssvm = Mockito.mock(EndPoint.class); + Mockito.when(epSelector.findSsvm(3L)).thenReturn(ssvm); + Answer ok = Mockito.mock(Answer.class); + Mockito.when(ok.getResult()).thenReturn(true); + Mockito.when(ssvm.sendMessage(Mockito.any())).thenReturn(ok); + + service.moveVolumeOnSecondaryStorageToAnotherAccount(volume, src, dst); + + ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(String.class); + Mockito.verify(storeVo).setInstallPath(pathCaptor.capture()); + Assert.assertTrue("install path should land in dest folder & keep filename: " + pathCaptor.getValue(), + pathCaptor.getValue().contains("/99/7/") && pathCaptor.getValue().endsWith("file.qcow2")); + } + + @Test(expected = CloudRuntimeException.class) + public void moveVolumeOnSecondaryThrowsWhenSsvmAnswerFails() { + Volume volume = Mockito.mock(Volume.class); + Mockito.when(volume.getId()).thenReturn(7L); + Mockito.when(volume.getUuid()).thenReturn("uuid-1"); + Mockito.when(volume.getName()).thenReturn("vol1"); + Mockito.when(volume.getDataCenterId()).thenReturn(3L); + Account src = Mockito.mock(Account.class); + Account dst = Mockito.mock(Account.class); + Mockito.when(dst.getAccountId()).thenReturn(99L); + + VolumeDataStoreVO storeVo = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(volumeStoreDao.findByVolume(7L)).thenReturn(storeVo); + + VolumeInfo info = Mockito.mock(VolumeInfo.class); + DataStore secStore = Mockito.mock(DataStore.class); + Mockito.when(info.getDataStore()).thenReturn(secStore); + Mockito.when(secStore.getUri()).thenReturn("nfs://store"); + Mockito.when(info.getPath()).thenReturn("/template/2/7/file.qcow2"); + Mockito.when(volFactory.getVolume(7L, DataStoreRole.Image)).thenReturn(info); + + EndPoint ssvm = Mockito.mock(EndPoint.class); + Mockito.when(epSelector.findSsvm(3L)).thenReturn(ssvm); + Answer bad = Mockito.mock(Answer.class); + Mockito.when(bad.getResult()).thenReturn(false); + Mockito.when(bad.getDetails()).thenReturn("no space"); + Mockito.when(ssvm.sendMessage(Mockito.any())).thenReturn(bad); + + service.moveVolumeOnSecondaryStorageToAnotherAccount(volume, src, dst); + } + + @Test(expected = CloudRuntimeException.class) + public void moveVolumeOnSecondaryThrowsWhenDbUpdateFails() { + Volume volume = Mockito.mock(Volume.class); + Mockito.when(volume.getId()).thenReturn(7L); + Mockito.when(volume.getUuid()).thenReturn("uuid-1"); + Mockito.when(volume.getName()).thenReturn("vol1"); + Mockito.when(volume.getDataCenterId()).thenReturn(3L); + Account src = Mockito.mock(Account.class); + Account dst = Mockito.mock(Account.class); + Mockito.when(dst.getAccountId()).thenReturn(99L); + + VolumeDataStoreVO storeVo = Mockito.mock(VolumeDataStoreVO.class); + Mockito.when(storeVo.getId()).thenReturn(101L); + Mockito.when(volumeStoreDao.findByVolume(7L)).thenReturn(storeVo); + Mockito.when(volumeStoreDao.update(Mockito.eq(101L), Mockito.any())).thenReturn(false); + + VolumeInfo info = Mockito.mock(VolumeInfo.class); + DataStore secStore = Mockito.mock(DataStore.class); + Mockito.when(info.getDataStore()).thenReturn(secStore); + Mockito.when(secStore.getUri()).thenReturn("nfs://store"); + Mockito.when(info.getPath()).thenReturn("/template/2/7/file.qcow2"); + Mockito.when(volFactory.getVolume(7L, DataStoreRole.Image)).thenReturn(info); + + EndPoint ssvm = Mockito.mock(EndPoint.class); + Mockito.when(epSelector.findSsvm(3L)).thenReturn(ssvm); + Answer ok = Mockito.mock(Answer.class); + Mockito.when(ok.getResult()).thenReturn(true); + Mockito.when(ssvm.sendMessage(Mockito.any())).thenReturn(ok); + + service.moveVolumeOnSecondaryStorageToAnotherAccount(volume, src, dst); + } + + // --- god-class delegation smoke test ---------------------------------- + + @Test + public void godClassDelegatesHandleVolumeSyncToService() { + VolumeServiceImpl impl = new VolumeServiceImpl(); + SecondaryStorageVolumeService svc = Mockito.mock(SecondaryStorageVolumeService.class); + impl.secondaryStorageVolumeService = svc; + + DataStore ds = Mockito.mock(DataStore.class); + impl.handleVolumeSync(ds); + + @SuppressWarnings("rawtypes") + ArgumentCaptor captor = ArgumentCaptor.forClass(BiFunction.class); + Mockito.verify(svc).handleVolumeSync(Mockito.eq(ds), captor.capture()); + Assert.assertNotNull(captor.getValue()); + } + + @Test + public void godClassDelegatesMoveToService() { + VolumeServiceImpl impl = new VolumeServiceImpl(); + SecondaryStorageVolumeService svc = Mockito.mock(SecondaryStorageVolumeService.class); + impl.secondaryStorageVolumeService = svc; + + Volume v = Mockito.mock(Volume.class); + Account a1 = Mockito.mock(Account.class); + Account a2 = Mockito.mock(Account.class); + impl.moveVolumeOnSecondaryStorageToAnotherAccount(v, a1, a2); + + Mockito.verify(svc).moveVolumeOnSecondaryStorageToAnotherAccount(v, a1, a2); + } + + // --- helper plumbing checks ------------------------------------------- + + @Test + public void serviceFieldsAreNotNullAfterSetup() { + Assert.assertNotNull(service.volumeStoreDao); + Assert.assertNotNull(service.volDao); + Assert.assertNotNull(service.volFactory); + Assert.assertNotNull(service.epSelector); + Assert.assertNotNull(service.resourceLimitMgr); + Assert.assertNotNull(service.accountMgr); + Assert.assertNotNull(service.alertMgr); + } + + @Test + public void handleVolumeSyncSkipsBodyWhenLockNotAcquired() { + // mock the lock so lock() returns false -> body should not run + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Mockito.when(store.getId()).thenReturn(99L); + + try (MockedStatic mocked = Mockito.mockStatic(GlobalLock.class)) { + GlobalLock noLock = Mockito.mock(GlobalLock.class); + Mockito.when(noLock.lock(Mockito.anyInt())).thenReturn(false); + mocked.when(() -> GlobalLock.getInternLock(Mockito.anyString())).thenReturn(noLock); + + spy.handleVolumeSync(store, noopDownloader()); + + // listVolume must never have been called + Mockito.verify(spy, Mockito.never()).listVolume(Mockito.any()); + Mockito.verify(noLock).releaseRef(); + } + } + + @Test + public void handleVolumeSyncIgnoresEmptyStoreAndEmptyDb() { + SecondaryStorageVolumeServiceImpl spy = Mockito.spy(service); + Mockito.doReturn(new HashMap()).when(spy).listVolume(store); + Mockito.when(store.getId()).thenReturn(11L); + Mockito.when(volumeStoreDao.listByStoreId(11L)).thenReturn(Collections.emptyList()); + + try (MockedStatic ignored = stubGlobalLock()) { + spy.handleVolumeSync(store, noopDownloader()); + } + + // nothing to update or delete + Mockito.verify(volumeStoreDao, Mockito.never()).update(Mockito.anyLong(), Mockito.any()); + Mockito.verify(volumeStoreDao, Mockito.never()).remove(Mockito.anyLong()); + } +} diff --git a/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java b/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java index 02e6adcc784b..f2e6fec65b51 100644 --- a/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java +++ b/engine/userdata/cloud-init/src/main/java/org/apache/cloudstack/userdata/CloudInitUserDataProvider.java @@ -27,13 +27,13 @@ import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; -import javax.mail.BodyPart; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.Session; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; +import jakarta.mail.BodyPart; +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.Session; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.CollectionUtils; @@ -41,7 +41,7 @@ import com.cloud.utils.component.AdapterBase; import com.cloud.utils.exception.CloudRuntimeException; -import com.sun.mail.util.BASE64DecoderStream; +import org.eclipse.angus.mail.util.BASE64DecoderStream; public class CloudInitUserDataProvider extends AdapterBase implements UserDataProvider { diff --git a/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java b/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java index 86b6a6fb6ea7..e46567bd7cb4 100644 --- a/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java +++ b/engine/userdata/cloud-init/src/test/java/org/apache/cloudstack/userdata/CloudInitUserDataProviderTest.java @@ -23,11 +23,11 @@ import java.util.Properties; import java.util.zip.GZIPOutputStream; -import javax.mail.BodyPart; -import javax.mail.MessagingException; -import javax.mail.Session; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; +import jakarta.mail.BodyPart; +import jakarta.mail.MessagingException; +import jakarta.mail.Session; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; import org.apache.commons.codec.binary.Base64; import org.junit.Assert; diff --git a/engine/userdata/pom.xml b/engine/userdata/pom.xml index 56c181ae0602..d8e575ae0290 100644 --- a/engine/userdata/pom.xml +++ b/engine/userdata/pom.xml @@ -39,9 +39,9 @@ ${project.version} - javax.activation - activation - 1.1.1 + jakarta.activation + jakarta.activation-api + 2.1.3 org.apache.cloudstack diff --git a/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java index 7c5692564c99..90cd7baf7b79 100644 --- a/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java +++ b/engine/userdata/src/main/java/org/apache/cloudstack/userdata/UserDataManagerImpl.java @@ -37,7 +37,7 @@ import com.cloud.utils.component.ManagerBase; import com.cloud.utils.exception.CloudRuntimeException; -import javax.inject.Inject; +import jakarta.inject.Inject; public class UserDataManagerImpl extends ManagerBase implements UserDataManager { @Inject diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterFenceManagerImpl.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterFenceManagerImpl.java index 203ebe6e3d45..b64bf48f1490 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterFenceManagerImpl.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterFenceManagerImpl.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.management.ManagementServerHost; diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java index 759948cb9c0c..c6191791abce 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java @@ -37,7 +37,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.command.ReconcileCommandService; diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java index 3e498b1fbec3..6f96c3468468 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java @@ -20,7 +20,7 @@ import java.util.Map; import java.util.Properties; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.ca.CAManager; diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerJoinVO.java b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerJoinVO.java index 673db160b3ca..893713f6e069 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerJoinVO.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerJoinVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.management.ManagementServerHost; diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerVO.java b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerVO.java index a381bb412e20..24403115c4a6 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerVO.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostPeerVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.management.ManagementServerHostPeer; diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java index 6c3b2a93994c..f708853d3e9a 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerHostVO.java @@ -19,16 +19,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.management.ManagementServerHost; import com.cloud.utils.db.GenericDao; diff --git a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerStatusVO.java b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerStatusVO.java index 6daeffe6255c..8dd3575eedd8 100644 --- a/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerStatusVO.java +++ b/framework/cluster/src/main/java/com/cloud/cluster/ManagementServerStatusVO.java @@ -21,14 +21,14 @@ import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; @Entity diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java index ef50064050f8..0c283f527fcd 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/ConfigKey.java @@ -87,7 +87,6 @@ public class ConfigKey { public static final Pair SUBGROUP_KVM = new Pair<>("KVM", 2L); public static final Pair SUBGROUP_VMWARE = new Pair<>("VMware", 3L); public static final Pair SUBGROUP_XENSERVER = new Pair<>("XenServer", 4L); - public static final Pair SUBGROUP_OVM = new Pair<>("OVM", 5L); public static final Pair SUBGROUP_BAREMETAL = new Pair<>("Baremetal", 6L); public static final Pair SUBGROUP_CONSOLE_PROXY_VM = new Pair<>("ConsoleProxyVM", 1L); public static final Pair SUBGROUP_SEC_STORAGE_VM = new Pair<>("SecStorageVM", 2L); diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationDaoImpl.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationDaoImpl.java index 5b941f8fccc6..5fd074c06c61 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationDaoImpl.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/dao/ConfigurationDaoImpl.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.impl.ConfigurationVO; diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java index 650c078a370e..4298a1e5d839 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigDepotImpl.java @@ -24,8 +24,8 @@ import java.util.List; import java.util.Set; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigDepotAdmin; diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java index 5c232b1b015c..b48f7dd501b8 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationGroupVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.framework.config.impl; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.config.ConfigurationGroup; diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java index cb529e1cca02..084c8d29335b 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationSubGroupVO.java @@ -16,12 +16,12 @@ // under the License. package org.apache.cloudstack.framework.config.impl; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.config.ConfigurationSubGroup; diff --git a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java index d12a41864b05..bce1d8606074 100644 --- a/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java +++ b/framework/config/src/main/java/org/apache/cloudstack/framework/config/impl/ConfigurationVO.java @@ -19,12 +19,12 @@ import java.util.Date; import java.util.List; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.framework.config.ConfigKey; diff --git a/framework/db/pom.xml b/framework/db/pom.xml index ced0f64c663d..335e77129082 100644 --- a/framework/db/pom.xml +++ b/framework/db/pom.xml @@ -33,8 +33,8 @@ ehcache-core - org.eclipse.persistence - javax.persistence + jakarta.persistence + jakarta.persistence-api org.apache.commons diff --git a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java index a3e3cce237dc..5d61a3d51d02 100644 --- a/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java +++ b/framework/db/src/main/java/com/cloud/utils/crypt/EncryptionSecretKeyChanger.java @@ -58,8 +58,8 @@ import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; -import javax.persistence.Column; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Table; /* * EncryptionSecretKeyChanger updates Management Secret Key / DB Secret Key or both. diff --git a/framework/db/src/main/java/com/cloud/utils/db/Attribute.java b/framework/db/src/main/java/com/cloud/utils/db/Attribute.java index 3e5128d97b45..d077ce08aa38 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/Attribute.java +++ b/framework/db/src/main/java/com/cloud/utils/db/Attribute.java @@ -18,14 +18,14 @@ import java.lang.reflect.Field; -import javax.persistence.AttributeOverride; -import javax.persistence.Column; -import javax.persistence.ElementCollection; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; /** * The Java annotation are somewhat incomplete. This gives better information diff --git a/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java b/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java index 88397f54d4f4..db84fea5da76 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java +++ b/framework/db/src/main/java/com/cloud/utils/db/DbUtil.java @@ -27,19 +27,19 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.AttributeOverride; -import javax.persistence.AttributeOverrides; -import javax.persistence.Column; -import javax.persistence.Embeddable; -import javax.persistence.Embedded; -import javax.persistence.EmbeddedId; -import javax.persistence.Id; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.PrimaryKeyJoinColumns; -import javax.persistence.SecondaryTable; -import javax.persistence.SecondaryTables; -import javax.persistence.Table; -import javax.persistence.Transient; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.AttributeOverrides; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Id; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.PrimaryKeyJoinColumns; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.SecondaryTables; +import jakarta.persistence.Table; +import jakarta.persistence.Transient; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java b/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java index dcda5bc33d55..3d68a5656a60 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java +++ b/framework/db/src/main/java/com/cloud/utils/db/EcInfo.java @@ -25,9 +25,9 @@ import java.util.List; import java.util.Set; -import javax.persistence.CollectionTable; -import javax.persistence.ElementCollection; -import javax.persistence.JoinColumn; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.JoinColumn; import com.cloud.utils.exception.CloudRuntimeException; diff --git a/framework/db/src/main/java/com/cloud/utils/db/Filter.java b/framework/db/src/main/java/com/cloud/utils/db/Filter.java index 375e508c55f1..a0a5f0ebcf32 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/Filter.java +++ b/framework/db/src/main/java/com/cloud/utils/db/Filter.java @@ -18,7 +18,7 @@ import java.lang.reflect.Field; -import javax.persistence.Column; +import jakarta.persistence.Column; import com.cloud.utils.Pair; import com.cloud.utils.ReflectUtil; diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java index dcd863465d1b..2b590c918a50 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java @@ -50,16 +50,16 @@ import java.util.concurrent.ConcurrentHashMap; import javax.naming.ConfigurationException; -import javax.persistence.AttributeConverter; -import javax.persistence.AttributeOverride; -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.EmbeddedId; -import javax.persistence.EntityExistsException; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Table; -import javax.persistence.TableGenerator; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.EntityExistsException; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Table; +import jakarta.persistence.TableGenerator; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ObjectUtils; @@ -80,12 +80,12 @@ import com.cloud.utils.net.Ip; import com.cloud.utils.net.NetUtils; -import net.sf.cglib.proxy.Callback; -import net.sf.cglib.proxy.CallbackFilter; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.Factory; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.NoOp; +import org.springframework.cglib.proxy.Callback; +import org.springframework.cglib.proxy.CallbackFilter; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.NoOp; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; @@ -513,7 +513,7 @@ public List customSearchIncludingRemoved(SearchCriteria sc, final Filt if (st == SelectType.Entity) { results.add((M)toEntityBean(rs, false)); } else if (st == SelectType.Fields || st == SelectType.Result) { - M m = sc.getResultType().newInstance(); + M m = sc.getResultType().getDeclaredConstructor().newInstance(); for (int j = 1; j <= fields.size(); j++) { setField(m, fields.get(j - 1), rs, j); } @@ -1908,11 +1908,13 @@ protected T toEntityBean(final ResultSet result, final boolean cache) throws SQL protected T toVO(ResultSet result, boolean cache) throws SQLException { T entity; try { - entity = _entityBeanType.newInstance(); + entity = _entityBeanType.getDeclaredConstructor().newInstance(); } catch (InstantiationException e1) { throw new CloudRuntimeException("Unable to instantiate entity", e1); } catch (IllegalAccessException e1) { throw new CloudRuntimeException("Illegal Access", e1); + } catch (ReflectiveOperationException e1) { + throw new CloudRuntimeException("Unable to instantiate entity", e1); } toEntityBean(result, entity); if (cache && _cache != null) { @@ -1987,13 +1989,15 @@ protected void loadCollection(T entity, Attribute attr) { } } else { try { - Collection coll = (Collection) ec.rawClass.newInstance(); + Collection coll = (Collection) ec.rawClass.getDeclaredConstructor().newInstance(); coll.addAll(lst); attr.field.set(entity, coll); } catch (IllegalAccessException e) { throw new CloudRuntimeException("Come on we screen for this stuff, don't we?", e); } catch (InstantiationException e) { throw new CloudRuntimeException("Never should happen", e); + } catch (ReflectiveOperationException e) { + throw new CloudRuntimeException("Never should happen", e); } } } diff --git a/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java b/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java index 512941f4ee3c..d9d4d9d130c6 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java +++ b/framework/db/src/main/java/com/cloud/utils/db/SearchBase.java @@ -25,17 +25,17 @@ import java.util.List; import java.util.Map; -import javax.persistence.Column; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.Transient; import com.cloud.utils.db.SearchCriteria.Func; import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.SearchCriteria.SelectType; import com.cloud.utils.exception.CloudRuntimeException; -import net.sf.cglib.proxy.Factory; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; diff --git a/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java b/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java index a59b73e5cee1..be6ec9841ba2 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java +++ b/framework/db/src/main/java/com/cloud/utils/db/SequenceFetcher.java @@ -27,7 +27,7 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import javax.persistence.TableGenerator; +import jakarta.persistence.TableGenerator; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/framework/db/src/main/java/com/cloud/utils/db/SqlGenerator.java b/framework/db/src/main/java/com/cloud/utils/db/SqlGenerator.java index e65fd8a5796e..7713526413d3 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/SqlGenerator.java +++ b/framework/db/src/main/java/com/cloud/utils/db/SqlGenerator.java @@ -27,21 +27,21 @@ import java.util.List; import java.util.Map; -import javax.persistence.AttributeOverride; -import javax.persistence.CollectionTable; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.DiscriminatorValue; -import javax.persistence.ElementCollection; -import javax.persistence.Embeddable; -import javax.persistence.Embedded; -import javax.persistence.EmbeddedId; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.MappedSuperclass; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.SecondaryTable; -import javax.persistence.TableGenerator; +import jakarta.persistence.AttributeOverride; +import jakarta.persistence.CollectionTable; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Embedded; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.SecondaryTable; +import jakarta.persistence.TableGenerator; import org.apache.commons.lang.ArrayUtils; import com.cloud.utils.Pair; diff --git a/framework/db/src/main/java/com/cloud/utils/db/UpdateBuilder.java b/framework/db/src/main/java/com/cloud/utils/db/UpdateBuilder.java index 997f58a9c256..597c40385d8f 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/UpdateBuilder.java +++ b/framework/db/src/main/java/com/cloud/utils/db/UpdateBuilder.java @@ -21,8 +21,8 @@ import java.util.HashMap; import java.util.Map; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; import com.cloud.utils.Ternary; import com.cloud.utils.exception.CloudRuntimeException; diff --git a/framework/db/src/main/java/com/cloud/utils/db/UpdateFilter.java b/framework/db/src/main/java/com/cloud/utils/db/UpdateFilter.java index 37b35b133d4c..3a2ce378a498 100644 --- a/framework/db/src/main/java/com/cloud/utils/db/UpdateFilter.java +++ b/framework/db/src/main/java/com/cloud/utils/db/UpdateFilter.java @@ -18,7 +18,7 @@ import java.lang.reflect.Method; -import net.sf.cglib.proxy.CallbackFilter; +import org.springframework.cglib.proxy.CallbackFilter; public class UpdateFilter implements CallbackFilter { @Override diff --git a/framework/db/src/test/java/com/cloud/utils/DbUtilTest.java b/framework/db/src/test/java/com/cloud/utils/DbUtilTest.java index 7ae7368e173e..ed0e9bcbc8a4 100644 --- a/framework/db/src/test/java/com/cloud/utils/DbUtilTest.java +++ b/framework/db/src/test/java/com/cloud/utils/DbUtilTest.java @@ -26,8 +26,8 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Column; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Table; import javax.sql.DataSource; import org.junit.After; diff --git a/framework/db/src/test/java/com/cloud/utils/db/DbAnnotatedBase.java b/framework/db/src/test/java/com/cloud/utils/db/DbAnnotatedBase.java index 8a010203b510..7046fc8a94bb 100644 --- a/framework/db/src/test/java/com/cloud/utils/db/DbAnnotatedBase.java +++ b/framework/db/src/test/java/com/cloud/utils/db/DbAnnotatedBase.java @@ -16,8 +16,8 @@ // under the License. package com.cloud.utils.db; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import junit.framework.Assert; diff --git a/framework/db/src/test/java/com/cloud/utils/db/DbTestVO.java b/framework/db/src/test/java/com/cloud/utils/db/DbTestVO.java index 5285bfe50fee..59f1cbc70065 100644 --- a/framework/db/src/test/java/com/cloud/utils/db/DbTestVO.java +++ b/framework/db/src/test/java/com/cloud/utils/db/DbTestVO.java @@ -16,12 +16,12 @@ // under the License. package com.cloud.utils.db; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "test") diff --git a/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java b/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java index ebf514f532f7..ce792b62ce14 100644 --- a/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java +++ b/framework/db/src/test/java/com/cloud/utils/db/GenericDaoBaseTest.java @@ -30,7 +30,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import javax.persistence.EntityExistsException; +import jakarta.persistence.EntityExistsException; @RunWith(MockitoJUnitRunner.class) public class GenericDaoBaseTest { diff --git a/framework/db/src/test/java/com/cloud/utils/db/TransactionContextBuilderTest.java b/framework/db/src/test/java/com/cloud/utils/db/TransactionContextBuilderTest.java index 3ec635c15d98..6dbb0f1bd2d0 100644 --- a/framework/db/src/test/java/com/cloud/utils/db/TransactionContextBuilderTest.java +++ b/framework/db/src/test/java/com/cloud/utils/db/TransactionContextBuilderTest.java @@ -18,7 +18,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventDistributorImpl.java b/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventDistributorImpl.java index a67ff5cc9266..bd77e2b8c840 100644 --- a/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventDistributorImpl.java +++ b/framework/events/src/main/java/org/apache/cloudstack/framework/events/EventDistributorImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/AddCustomActionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/AddCustomActionCmd.java index dcea754430cf..74fb5f88b80f 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/AddCustomActionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/AddCustomActionCmd.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java index 9d76a7e6ec25..acb8ce6ffb8b 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/CreateExtensionCmd.java @@ -20,7 +20,7 @@ import java.util.EnumSet; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteCustomActionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteCustomActionCmd.java index 6f2153ad6bce..b5df2ad17906 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteCustomActionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteCustomActionCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.framework.extensions.api; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java index bef68c2d277b..4fe4d85b5fbf 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/DeleteExtensionCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.framework.extensions.api; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListCustomActionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListCustomActionCmd.java index 4f492bd20a6b..3543775c84fa 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListCustomActionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListCustomActionCmd.java @@ -19,7 +19,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListExtensionsCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListExtensionsCmd.java index 4426f259380b..77c632f2cf08 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListExtensionsCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/ListExtensionsCmd.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RegisterExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RegisterExtensionCmd.java index e8f71d7ac8c4..ebeb687df3bf 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RegisterExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RegisterExtensionCmd.java @@ -20,7 +20,7 @@ import java.util.EnumSet; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RunCustomActionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RunCustomActionCmd.java index 9e4c2cc27331..d2b20dea6829 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RunCustomActionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/RunCustomActionCmd.java @@ -19,7 +19,7 @@ import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UnregisterExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UnregisterExtensionCmd.java index 0edc7a247fda..51d03b1e71a3 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UnregisterExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UnregisterExtensionCmd.java @@ -19,7 +19,7 @@ import java.util.EnumSet; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateCustomActionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateCustomActionCmd.java index bb03be00c5d5..ad67b7e3c7dd 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateCustomActionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateCustomActionCmd.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java index 5baaea1709db..a79f5432ff81 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/api/UpdateExtensionCmd.java @@ -20,7 +20,7 @@ import java.util.EnumSet; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java index f6fd08b6da2c..2e62b7c4a406 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/manager/ExtensionsManagerImpl.java @@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.Role; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionDetailsVO.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionDetailsVO.java index 15a5af4f60c3..ce78798f399a 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionDetailsVO.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionDetailsVO.java @@ -19,12 +19,12 @@ import org.apache.cloudstack.api.ResourceDetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "extension_custom_action_details") diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionVO.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionVO.java index c5ab288d853e..e35777b9e695 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionVO.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionCustomActionVO.java @@ -21,16 +21,16 @@ import com.cloud.utils.db.GenericDao; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; import java.util.UUID; diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionDetailsVO.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionDetailsVO.java index 535a0f703958..9a9117fed6cb 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionDetailsVO.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionDetailsVO.java @@ -19,12 +19,12 @@ import org.apache.cloudstack.api.ResourceDetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "extension_details") diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapDetailsVO.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapDetailsVO.java index 5cb6f7b85114..9a3379f45427 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapDetailsVO.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapDetailsVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.ResourceDetail; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "extension_resource_map_details") diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapVO.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapVO.java index 48d70b937e35..dd042ee44c56 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapVO.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionResourceMapVO.java @@ -21,16 +21,16 @@ import org.apache.cloudstack.extension.ExtensionResourceMap; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; @Entity diff --git a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionVO.java b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionVO.java index 20423764c1c3..1c17d7aab89b 100644 --- a/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionVO.java +++ b/framework/extensions/src/main/java/org/apache/cloudstack/framework/extensions/vo/ExtensionVO.java @@ -20,16 +20,16 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.extension.Extension; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallbackDispatcher.java b/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallbackDispatcher.java index 2eafe21e5936..2f23596346c5 100644 --- a/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallbackDispatcher.java +++ b/framework/ipc/src/main/java/org/apache/cloudstack/framework/async/AsyncCallbackDispatcher.java @@ -24,10 +24,10 @@ import java.util.HashMap; import java.util.Map; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.Factory; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.Factory; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallbackDispatcher.java b/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallbackDispatcher.java index 38ec33780aee..8ad11e8826e6 100644 --- a/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallbackDispatcher.java +++ b/framework/ipc/src/main/java/org/apache/cloudstack/framework/rpc/RpcCallbackDispatcher.java @@ -21,9 +21,9 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.MethodInterceptor; -import net.sf.cglib.proxy.MethodProxy; +import org.springframework.cglib.proxy.Enhancer; +import org.springframework.cglib.proxy.MethodInterceptor; +import org.springframework.cglib.proxy.MethodProxy; public class RpcCallbackDispatcher { private Method _callbackMethod; diff --git a/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent.java b/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent.java index 77a2a72066ef..94c826b0cd60 100644 --- a/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent.java +++ b/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent.java @@ -21,8 +21,8 @@ import java.util.Timer; import java.util.TimerTask; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent2.java b/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent2.java index 294d1c979eb9..201954fff46e 100644 --- a/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent2.java +++ b/framework/ipc/src/test/java/org/apache/cloudstack/framework/sampleserver/SampleManagerComponent2.java @@ -18,8 +18,8 @@ */ package org.apache.cloudstack.framework.sampleserver; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/framework/ipc/src/test/java/org/apache/cloudstack/messagebus/TestMessageBus.java b/framework/ipc/src/test/java/org/apache/cloudstack/messagebus/TestMessageBus.java index 5dd38642258a..accea0f3cce8 100644 --- a/framework/ipc/src/test/java/org/apache/cloudstack/messagebus/TestMessageBus.java +++ b/framework/ipc/src/test/java/org/apache/cloudstack/messagebus/TestMessageBus.java @@ -18,7 +18,7 @@ */ package org.apache.cloudstack.messagebus; -import javax.inject.Inject; +import jakarta.inject.Inject; import junit.framework.TestCase; diff --git a/framework/jobs/pom.xml b/framework/jobs/pom.xml index ad342acf8d83..3a0f998fec3d 100644 --- a/framework/jobs/pom.xml +++ b/framework/jobs/pom.xml @@ -73,5 +73,11 @@ commons-io test + + + io.opentelemetry + opentelemetry-api + diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java index a467b5fdf59e..8cddaf9c9014 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/dao/VmWorkJobDaoImpl.java @@ -21,8 +21,8 @@ import java.util.Date; import java.util.List; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJoinMapVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJoinMapVO.java index a9647361d2e0..17fc31d44965 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJoinMapVO.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJoinMapVO.java @@ -18,16 +18,16 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.jobs.JobInfo; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJournalVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJournalVO.java index 3079a64d28dd..4775e1d0960a 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJournalVO.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobJournalVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.framework.jobs.AsyncJob; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java index 7672b9dc6f97..b7579d44d7e4 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobManagerImpl.java @@ -32,7 +32,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.storage.SnapshotVO; @@ -54,6 +54,14 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.jobs.AsyncJob; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Scope; + import org.apache.cloudstack.framework.jobs.AsyncJobDispatcher; import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; import org.apache.cloudstack.framework.jobs.AsyncJobManager; @@ -599,6 +607,37 @@ private AsyncJobDispatcher getDispatcher(String dispatcherName) { throw new CloudRuntimeException("Unable to find dispatcher name: " + dispatcherName); } + private static final String JOB_TRACER_NAME = "org.apache.cloudstack.jobs"; + + /** + * Wraps async job execution in an OpenTelemetry SERVER span so individual + * jobs show up in trace stores. The span has no parent link to the + * originating API request (trace context isn't currently persisted with the + * job row) — each job execution is its own trace root for now. + * + *

If OpenTelemetry isn't initialized in this JVM, + * {@link GlobalOpenTelemetry#get()} returns the no-op instance and this + * helper degenerates to a plain {@code dispatcher.runJob(job)} call. + */ + private void executeWithSpan(AsyncJob job, AsyncJobDispatcher jobDispatcher) { + Tracer tracer = GlobalOpenTelemetry.get().getTracer(JOB_TRACER_NAME); + Span span = tracer.spanBuilder("asyncjob " + job.getCmd()) + .setSpanKind(SpanKind.INTERNAL) + .setAttribute("cloudstack.job.id", job.getId()) + .setAttribute("cloudstack.job.cmd", job.getCmd() == null ? "" : job.getCmd()) + .setAttribute("cloudstack.job.dispatcher", job.getDispatcher() == null ? "" : job.getDispatcher()) + .startSpan(); + try (Scope ignored = span.makeCurrent()) { + jobDispatcher.runJob(job); + } catch (Throwable t) { + span.recordException(t); + span.setStatus(StatusCode.ERROR, t.getClass().getSimpleName()); + throw t; + } finally { + span.end(); + } + } + private AsyncJobDispatcher findWakeupDispatcher(AsyncJob job) { if (_jobDispatchers != null) { List joinRecords = _joinMapDao.listJoinRecords(job.getId()); @@ -695,7 +734,7 @@ protected void runInContext() { } else { AsyncJobDispatcher jobDispatcher = getDispatcher(job.getDispatcher()); if (jobDispatcher != null) { - jobDispatcher.runJob(job); + executeWithSpan(job, jobDispatcher); } else { logger.error("Unable to find job dispatcher, job will be cancelled"); completeAsyncJob(job.getId(), JobInfo.Status.FAILED, ApiErrorCode.INTERNAL_ERROR.getHttpCode(), null); diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobMonitor.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobMonitor.java index b2216cb75025..c4afd041b3a0 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobMonitor.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobMonitor.java @@ -22,7 +22,7 @@ import java.util.Timer; import java.util.concurrent.atomic.AtomicInteger; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java index 0f2c8d1736a4..cf12a286269b 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/AsyncJobVO.java @@ -19,21 +19,21 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.DiscriminatorColumn; -import javax.persistence.DiscriminatorType; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Inheritance; -import javax.persistence.InheritanceType; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; +import jakarta.persistence.Transient; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.jobs.JobInfo; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueItemVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueItemVO.java index cbdb78d4d556..c6b8b11f5a0d 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueItemVO.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueItemVO.java @@ -18,14 +18,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueManagerImpl.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueManagerImpl.java index 3397daa58191..eec456cda818 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueManagerImpl.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueManagerImpl.java @@ -20,7 +20,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.jobs.dao.SyncQueueDao; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueVO.java index 9e4093b5818c..a7e69f3984b6 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueVO.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/SyncQueueVO.java @@ -19,14 +19,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/VmWorkJobVO.java b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/VmWorkJobVO.java index 41eaac598bf3..0c86d829bf89 100644 --- a/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/VmWorkJobVO.java +++ b/framework/jobs/src/main/java/org/apache/cloudstack/framework/jobs/impl/VmWorkJobVO.java @@ -16,13 +16,13 @@ // under the License. package org.apache.cloudstack.framework.jobs.impl; -import javax.persistence.Column; -import javax.persistence.DiscriminatorValue; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.PrimaryKeyJoinColumn; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.PrimaryKeyJoinColumn; +import jakarta.persistence.Table; import com.cloud.vm.VirtualMachine; diff --git a/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobTestDispatcher.java b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobTestDispatcher.java index 604eae74afc3..11638c05755c 100644 --- a/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobTestDispatcher.java +++ b/framework/jobs/src/test/java/org/apache/cloudstack/framework/jobs/AsyncJobTestDispatcher.java @@ -18,7 +18,7 @@ import java.util.Random; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.jobs.JobInfo.Status; diff --git a/framework/jobs/src/test/resources/log4j.properties b/framework/jobs/src/test/resources/log4j.properties index 7ffdca8aea52..4253a201c255 100644 --- a/framework/jobs/src/test/resources/log4j.properties +++ b/framework/jobs/src/test/resources/log4j.properties @@ -30,5 +30,4 @@ log4j.appender.rolling.DatePattern='.'yyy-MM-dd log4j.appender.rolling.file.append=false log4j.category.org.apache=DEBUG, rolling, stdout #log4j.category.com.cloud.utils.db.Transaction=ALL -log4j.category.org.apache.cloudstack.network.contrail=ALL log4j.category.com.cloud.network=ALL diff --git a/framework/quota/pom.xml b/framework/quota/pom.xml index 70cb3ac8cd53..6faa772f6611 100644 --- a/framework/quota/pom.xml +++ b/framework/quota/pom.xml @@ -43,8 +43,8 @@ commons-lang3 - javax.mail - mail + jakarta.mail + jakarta.mail-api diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java index b26b3171f5b5..09cb3e9be042 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaAlertManagerImpl.java @@ -26,7 +26,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.DateUtil; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java index 816144aa2f16..1f2b7d01bb26 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaManagerImpl.java @@ -29,7 +29,7 @@ import java.util.TimeZone; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java index 5ee327fb9a5c..f60aac5ace25 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/QuotaStatementImpl.java @@ -24,7 +24,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java index 23020292027c..932ccb355649 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelper.java @@ -32,7 +32,7 @@ import com.cloud.network.dao.NetworkVO; import com.cloud.network.vpc.VpcOfferingVO; import com.cloud.network.vpc.VpcVO; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.storage.StoragePoolTagVO; import com.cloud.vm.VirtualMachine; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java index ce51177d0aec..88201e91ae92 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaCreditsDaoImpl.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.domain.dao.DomainDao; import com.cloud.utils.db.Filter; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java index 9466340ad053..25edb7bb2869 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaEmailConfigurationDaoImpl.java @@ -28,8 +28,8 @@ import org.apache.cloudstack.quota.vo.QuotaEmailTemplatesVO; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import java.util.List; @Component diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java index 556f552fed69..65e5b25133f5 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaTariffUsageDaoImpl.java @@ -26,7 +26,7 @@ import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionLegacy; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.List; @Component diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java index b98ea2b3a5d2..b9750a1d1012 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaUsageJoinDaoImpl.java @@ -29,8 +29,8 @@ import org.apache.commons.lang3.ObjectUtils; import org.springframework.stereotype.Component; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import java.util.Date; import java.util.List; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaAccountVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaAccountVO.java index 00bc33a98dc8..4dac6e43ff43 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaAccountVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaAccountVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.math.BigDecimal; import java.util.Date; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java index 509702a86718..afeb9dcadcad 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaBalanceVO.java @@ -18,12 +18,12 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.math.BigDecimal; import java.util.Date; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java index 5cf6b1f0e575..cf20e9a19db2 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaCreditsVO.java @@ -19,12 +19,12 @@ import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.math.BigDecimal; import java.util.Date; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java index e50c7ce62504..c7cd7e0c723e 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailConfigurationVO.java @@ -16,9 +16,9 @@ // under the License. package org.apache.cloudstack.quota.vo; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; @Entity @Table(name = "quota_email_configuration") diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java index 1ad4b379b566..e69ca5ec1ffd 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaEmailTemplatesVO.java @@ -18,14 +18,14 @@ import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.util.Date; @Entity diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java index f9796497d57d..976a9a10c727 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaSummaryVO.java @@ -20,14 +20,14 @@ import java.math.BigDecimal; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import com.cloud.user.Account; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java index 4fa9e771713e..918551a3d367 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffUsageVO.java @@ -18,10 +18,10 @@ import java.math.BigDecimal; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.InternalIdentity; import org.apache.commons.lang3.builder.ReflectionToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java index bd6aeb134180..4d0f2200016c 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaTariffVO.java @@ -24,14 +24,14 @@ import com.cloud.utils.db.GenericDao; import org.apache.commons.lang3.StringUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.math.BigDecimal; import java.util.Date; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java index df9577e23c3e..9953a1caf934 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageJoinVO.java @@ -19,12 +19,12 @@ import org.apache.cloudstack.api.InternalIdentity; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import java.math.BigDecimal; import java.util.Date; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageVO.java index 2a26951237ea..55250bd7b885 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/QuotaUsageVO.java @@ -19,12 +19,12 @@ import java.math.BigDecimal; import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.InternalIdentity; diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/VMInstanceDetailVO.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/VMInstanceDetailVO.java index c5ca590e95ce..8cea03378721 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/VMInstanceDetailVO.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/vo/VMInstanceDetailVO.java @@ -16,12 +16,12 @@ //under the License. package org.apache.cloudstack.quota.vo; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.api.ResourceDetail; diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java index 54d4f1d5b690..1cbd93f214de 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/QuotaAlertManagerImplTest.java @@ -24,7 +24,7 @@ import java.util.List; import java.util.TimeZone; -import javax.mail.MessagingException; +import jakarta.mail.MessagingException; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; diff --git a/framework/rest/pom.xml b/framework/rest/pom.xml index e2e787aec460..296f1cdd44e0 100644 --- a/framework/rest/pom.xml +++ b/framework/rest/pom.xml @@ -56,20 +56,25 @@ ${cs.jackson.version} - javax.xml.bind - jaxb-api + jakarta.xml.bind + jakarta.xml.bind-api ${cs.jaxb.version} - com.sun.xml.bind + org.glassfish.jaxb jaxb-core - ${cs.jaxb.version} + ${cs.jaxb.impl.version} - com.sun.xml.bind - jaxb-impl + org.glassfish.jaxb + jaxb-runtime ${cs.jaxb.impl.version} + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + org.apache.cxf cxf-rt-frontend-jaxrs @@ -79,14 +84,6 @@ org.eclipse.jetty jetty-server - - org.apache.geronimo.specs - geronimo-servlet_3.0_spec - - - com.sun.xml.bind - jaxb-impl - diff --git a/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UriSerializer.java b/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UriSerializer.java index 07951496419a..b03ef681effd 100644 --- a/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UriSerializer.java +++ b/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UriSerializer.java @@ -20,7 +20,7 @@ import java.io.IOException; -import javax.ws.rs.core.UriBuilder; +import jakarta.ws.rs.core.UriBuilder; import org.apache.cxf.jaxrs.impl.tl.ThreadLocalUriInfo; diff --git a/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UrisSerializer.java b/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UrisSerializer.java index 0ea5886a86e6..6e1dcdbab5ff 100644 --- a/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UrisSerializer.java +++ b/framework/rest/src/main/java/org/apache/cloudstack/framework/ws/jackson/UrisSerializer.java @@ -22,7 +22,7 @@ import java.util.Iterator; import java.util.List; -import javax.ws.rs.core.UriBuilder; +import jakarta.ws.rs.core.UriBuilder; import org.apache.cxf.jaxrs.impl.tl.ThreadLocalUriInfo; diff --git a/framework/rest/src/test/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationTest.java b/framework/rest/src/test/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationTest.java index d956e345f0d4..b792709282b0 100644 --- a/framework/rest/src/test/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationTest.java +++ b/framework/rest/src/test/java/org/apache/cloudstack/framework/ws/jackson/CSJacksonAnnotationTest.java @@ -23,8 +23,8 @@ import java.util.List; import java.util.Map; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; import org.junit.Before; import org.junit.Ignore; diff --git a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keys/KeysManagerImpl.java b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keys/KeysManagerImpl.java index fa092ebdd3c6..adde93aa9cd8 100644 --- a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keys/KeysManagerImpl.java +++ b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keys/KeysManagerImpl.java @@ -19,7 +19,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.net.ssl.KeyManager; import org.apache.commons.codec.binary.Base64; diff --git a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java index 3e01942fb2b1..41531fc2304d 100644 --- a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java +++ b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java @@ -28,7 +28,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.utils.Pair; import org.apache.commons.lang3.StringUtils; diff --git a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreVO.java b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreVO.java index e243500447d5..367381a162f0 100644 --- a/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreVO.java +++ b/framework/security/src/main/java/org/apache/cloudstack/framework/security/keystore/KeystoreVO.java @@ -19,12 +19,12 @@ import com.cloud.utils.db.Encrypt; import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "keystore") diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/ConfigDepotLifeCycle.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/ConfigDepotLifeCycle.java index 167c3c2cad4f..ccc7b16cd59a 100644 --- a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/ConfigDepotLifeCycle.java +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/ConfigDepotLifeCycle.java @@ -18,7 +18,7 @@ */ package org.apache.cloudstack.spring.lifecycle; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.springframework.beans.BeansException; import org.springframework.beans.factory.config.BeanPostProcessor; diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/DumpRegistry.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/DumpRegistry.java index 3a9bb04ce967..0f0a8a76ecf4 100644 --- a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/DumpRegistry.java +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/DumpRegistry.java @@ -20,7 +20,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.utils.component.ComponentLifecycleBase; diff --git a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java index 47aa82b9dc02..b240cdc6002e 100644 --- a/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java +++ b/framework/spring/lifecycle/src/main/java/org/apache/cloudstack/spring/lifecycle/registry/ExtensionRegistry.java @@ -25,7 +25,7 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Logger; diff --git a/framework/spring/module/pom.xml b/framework/spring/module/pom.xml index f44cffa30b10..fb0159842cdb 100644 --- a/framework/spring/module/pom.xml +++ b/framework/spring/module/pom.xml @@ -42,8 +42,8 @@ spring-web - javax.servlet - javax.servlet-api + jakarta.servlet + jakarta.servlet-api provided true diff --git a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/CloudStackContextLoaderListener.java b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/CloudStackContextLoaderListener.java index 3b6133b91b4a..e7e0a8e46a81 100644 --- a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/CloudStackContextLoaderListener.java +++ b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/CloudStackContextLoaderListener.java @@ -20,8 +20,8 @@ import java.io.IOException; -import javax.servlet.ServletContext; -import javax.servlet.ServletContextEvent; +import jakarta.servlet.ServletContext; +import jakarta.servlet.ServletContextEvent; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; diff --git a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/ModuleBasedFilter.java b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/ModuleBasedFilter.java index b3648ab76e55..f457e6b3f64b 100644 --- a/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/ModuleBasedFilter.java +++ b/framework/spring/module/src/main/java/org/apache/cloudstack/spring/module/web/ModuleBasedFilter.java @@ -18,9 +18,9 @@ */ package org.apache.cloudstack.spring.module.web; -import javax.servlet.Filter; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; diff --git a/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/InitTest.java b/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/InitTest.java index 1f17075fb5e9..d3f826bfdbb4 100644 --- a/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/InitTest.java +++ b/framework/spring/module/src/test/java/org/apache/cloudstack/spring/module/factory/InitTest.java @@ -18,7 +18,7 @@ */ package org.apache.cloudstack.spring.module.factory; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; public class InitTest { diff --git a/packaging/el8/cloud.spec b/packaging/el8/cloud.spec index 3dee161bf274..7af68f6349fd 100644 --- a/packaging/el8/cloud.spec +++ b/packaging/el8/cloud.spec @@ -127,6 +127,8 @@ Requires: rng-tools Requires: (libgcrypt > 1.8.3 or libgcrypt20) Requires: (selinux-tools if selinux-tools) Requires: sysstat +Requires: python3-libnbd +Requires: socat Provides: cloud-agent Group: System Environment/Libraries %description agent diff --git a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java index f3e2335519a4..b2c92440fae5 100644 --- a/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java +++ b/plugins/acl/dynamic-role-based/src/main/java/org/apache/cloudstack/acl/DynamicRoleBasedAPIAccessChecker.java @@ -25,7 +25,7 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; diff --git a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java index 8513f458660c..026e3723c6d4 100644 --- a/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java +++ b/plugins/acl/project-role-based/src/main/java/org/apache/cloudstack/acl/ProjectRoleBasedApiAccessChecker.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.RolePermissionEntity.Permission; diff --git a/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java b/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java index 6cf4da88f5c8..19a28871b775 100644 --- a/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java +++ b/plugins/acl/static-role-based/src/main/java/org/apache/cloudstack/acl/StaticRoleBasedAPIAccessChecker.java @@ -23,7 +23,7 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.exception.UnavailableCommandException; diff --git a/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java b/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java index 667b475eada1..b6d66eff8d12 100644 --- a/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java +++ b/plugins/affinity-group-processors/explicit-dedication/src/main/java/org/apache/cloudstack/affinity/ExplicitDedicationProcessor.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; diff --git a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java index b97b8e224ad7..529f8af6b4f1 100644 --- a/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-affinity/src/main/java/org/apache/cloudstack/affinity/HostAffinityProcessor.java @@ -25,7 +25,7 @@ import java.util.ArrayList; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; diff --git a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java index bd29a48f2588..25582b5df8a2 100644 --- a/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java +++ b/plugins/affinity-group-processors/host-anti-affinity/src/main/java/org/apache/cloudstack/affinity/HostAntiAffinityProcessor.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.commons.collections.CollectionUtils; diff --git a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java index 49e3f60ed5d0..0d252892bfff 100644 --- a/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java +++ b/plugins/affinity-group-processors/non-strict-host-affinity/src/main/java/org/apache/cloudstack/affinity/NonStrictHostAffinityProcessor.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; diff --git a/plugins/api/discovery/pom.xml b/plugins/api/discovery/pom.xml index 1992da81e5fb..033b959b5090 100644 --- a/plugins/api/discovery/pom.xml +++ b/plugins/api/discovery/pom.xml @@ -52,7 +52,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} -Xmx1024m + @{argLine} -javaagent:${org.mockito:mockito-core:jar} -Xmx1024m org/apache/cloudstack/discovery/integration/* diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java index 8cd31939da01..0a8ad468d103 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/api/command/user/discovery/ListApisCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.discovery; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; diff --git a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java index d412f12fce24..64c0382c1c69 100644 --- a/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java +++ b/plugins/api/discovery/src/main/java/org/apache/cloudstack/discovery/ApiDiscoveryServiceImpl.java @@ -28,7 +28,7 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.APIChecker; import org.apache.cloudstack.acl.Role; diff --git a/plugins/api/rate-limit/pom.xml b/plugins/api/rate-limit/pom.xml index 294ff18bcc9b..d44e42197cbe 100644 --- a/plugins/api/rate-limit/pom.xml +++ b/plugins/api/rate-limit/pom.xml @@ -34,7 +34,7 @@ maven-surefire-plugin always - @{argLine} -Xmx2048m -XX:MaxMetaspaceSize=1024m + @{argLine} -javaagent:${org.mockito:mockito-core:jar} -Xmx2048m -XX:MaxMetaspaceSize=1024m org/apache/cloudstack/ratelimit/integration/* diff --git a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java index 663b4fa83544..0234a15e705c 100644 --- a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java +++ b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/admin/ratelimit/ResetApiLimitCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.ratelimit; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ACL; diff --git a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java index eafe2782d33f..cd5e08e9563e 100644 --- a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java +++ b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/api/command/user/ratelimit/GetApiLimitCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.ratelimit; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java index afa2b6155de6..7b924365508a 100644 --- a/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java +++ b/plugins/api/rate-limit/src/main/java/org/apache/cloudstack/ratelimit/ApiRateLimitServiceImpl.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import net.sf.ehcache.Cache; diff --git a/plugins/api/solidfire-intg-test/pom.xml b/plugins/api/solidfire-intg-test/pom.xml deleted file mode 100644 index 0cc9d1aba5a2..000000000000 --- a/plugins/api/solidfire-intg-test/pom.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - 4.0.0 - cloud-plugin-api-solidfire-intg-test - Apache CloudStack Plugin - API SolidFire Integration Testing - - org.apache.cloudstack - cloudstack-plugins - 4.23.0.0-SNAPSHOT - ../../pom.xml - - - - org.apache.cloudstack - cloud-plugin-storage-volume-solidfire - ${project.version} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - @{argLine} -Xmx1024m - - org/apache/cloudstack/solidfire/integration/* - - - - - - diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetPathForVolumeCmd.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetPathForVolumeCmd.java deleted file mode 100644 index 5ff7f82a7b0e..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetPathForVolumeCmd.java +++ /dev/null @@ -1,59 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.solidfire; - -import javax.inject.Inject; - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.solidfire.ApiPathForVolumeResponse; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; - -@APICommand(name = "getPathForVolume", responseObject = ApiPathForVolumeResponse.class, description = "Get the path associated with the provided volume UUID", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class GetPathForVolumeCmd extends BaseCmd { - - @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, description = "CloudStack Volume UUID", required = true) - private String _volumeUuid; - - @Inject private SolidFireIntegrationTestUtil _util; - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public long getEntityOwnerId() { - return _util.getAccountIdForVolumeUuid(_volumeUuid); - } - - @Override - public void execute() { - logger.info("'GetPathForVolumeIdCmd.execute' method invoked"); - - String pathForVolume = _util.getPathForVolumeUuid(_volumeUuid); - - ApiPathForVolumeResponse response = new ApiPathForVolumeResponse(pathForVolume); - - response.setResponseName(getCommandName()); - response.setObjectName("apipathforvolume"); - - setResponseObject(response); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireAccountIdCmd.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireAccountIdCmd.java deleted file mode 100644 index baedb0389d55..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireAccountIdCmd.java +++ /dev/null @@ -1,64 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.solidfire; - -import javax.inject.Inject; - - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.solidfire.ApiSolidFireAccountIdResponse; -import org.apache.cloudstack.solidfire.SolidFireIntegrationTestManager; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; - -@APICommand(name = "getSolidFireAccountId", responseObject = ApiSolidFireAccountIdResponse.class, description = "Get SolidFire Account ID", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class GetSolidFireAccountIdCmd extends BaseCmd { - - @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.STRING, description = "CloudStack Account UUID", required = true) - private String csAccountUuid; - @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.STRING, description = "Storage Pool UUID", required = true) - private String storagePoolUuid; - - @Inject private SolidFireIntegrationTestManager manager; - @Inject private SolidFireIntegrationTestUtil util; - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public long getEntityOwnerId() { - return util.getAccountIdForAccountUuid(csAccountUuid); - } - - @Override - public void execute() { - logger.info("'GetSolidFireAccountIdCmd.execute' method invoked"); - - long sfAccountId = manager.getSolidFireAccountId(csAccountUuid, storagePoolUuid); - - ApiSolidFireAccountIdResponse response = new ApiSolidFireAccountIdResponse(sfAccountId); - - response.setResponseName(getCommandName()); - response.setObjectName("apisolidfireaccountid"); - - this.setResponseObject(response); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireVolumeAccessGroupIdsCmd.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireVolumeAccessGroupIdsCmd.java deleted file mode 100644 index c250c870f8d7..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireVolumeAccessGroupIdsCmd.java +++ /dev/null @@ -1,73 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.solidfire; - -import com.cloud.user.Account; - -import javax.inject.Inject; - - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.solidfire.ApiSolidFireVolumeAccessGroupIdsResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.solidfire.SolidFireIntegrationTestManager; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; - -@APICommand(name = "getSolidFireVolumeAccessGroupIds", responseObject = ApiSolidFireVolumeAccessGroupIdsResponse.class, description = "Get the SF Volume Access Group IDs", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class GetSolidFireVolumeAccessGroupIdsCmd extends BaseCmd { - - @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.STRING, description = "Cluster UUID", required = true) - private String clusterUuid; - @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.STRING, description = "Storage Pool UUID", required = true) - private String storagePoolUuid; - - @Inject private SolidFireIntegrationTestManager manager; - @Inject private SolidFireIntegrationTestUtil util; - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public long getEntityOwnerId() { - Account account = CallContext.current().getCallingAccount(); - - if (account != null) { - return account.getId(); - } - - return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked - } - - @Override - public void execute() { - logger.info("'GetSolidFireVolumeAccessGroupIdsCmd.execute' method invoked"); - - long[] sfVagIds = manager.getSolidFireVolumeAccessGroupIds(clusterUuid, storagePoolUuid); - - ApiSolidFireVolumeAccessGroupIdsResponse response = new ApiSolidFireVolumeAccessGroupIdsResponse(sfVagIds); - - response.setResponseName(getCommandName()); - response.setObjectName("apisolidfirevolumeaccessgroupids"); - - this.setResponseObject(response); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireVolumeSizeCmd.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireVolumeSizeCmd.java deleted file mode 100644 index 10af3be25b06..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetSolidFireVolumeSizeCmd.java +++ /dev/null @@ -1,62 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.solidfire; - -import javax.inject.Inject; - - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.solidfire.ApiSolidFireVolumeSizeResponse; -import org.apache.cloudstack.solidfire.SolidFireIntegrationTestManager; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; - -@APICommand(name = "getSolidFireVolumeSize", responseObject = ApiSolidFireVolumeSizeResponse.class, description = "Get the SF volume size including Hypervisor Snapshot Reserve", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class GetSolidFireVolumeSizeCmd extends BaseCmd { - - @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, description = "Volume UUID", required = true) - private String volumeUuid; - - @Inject private SolidFireIntegrationTestManager manager; - @Inject private SolidFireIntegrationTestUtil util; - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public long getEntityOwnerId() { - return util.getAccountIdForVolumeUuid(volumeUuid); - } - - @Override - public void execute() { - logger.info("'GetSolidFireVolumeSizeCmd.execute' method invoked"); - - long sfVolumeSize = manager.getSolidFireVolumeSize(volumeUuid); - - ApiSolidFireVolumeSizeResponse response = new ApiSolidFireVolumeSizeResponse(sfVolumeSize); - - response.setResponseName(getCommandName()); - response.setObjectName("apisolidfirevolumesize"); - - this.setResponseObject(response); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetVolumeSnapshotDetailsCmd.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetVolumeSnapshotDetailsCmd.java deleted file mode 100644 index bbb86bec31ed..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetVolumeSnapshotDetailsCmd.java +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.solidfire; - -import java.util.List; - -import javax.inject.Inject; - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.solidfire.ApiVolumeSnapshotDetailsResponse; -import org.apache.cloudstack.api.response.solidfire.ApiVolumeiScsiNameResponse; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; - -@APICommand(name = "getVolumeSnapshotDetails", responseObject = ApiVolumeiScsiNameResponse.class, description = "Get Volume Snapshot Details", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) - -public class GetVolumeSnapshotDetailsCmd extends BaseCmd { - - @Parameter(name = ApiConstants.SNAPSHOT_ID, type = CommandType.STRING, description = "CloudStack Snapshot UUID", required = true) - private String snapshotUuid; - - @Inject private SolidFireIntegrationTestUtil util; - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public long getEntityOwnerId() { - return util.getAccountIdForSnapshotUuid(snapshotUuid); - } - - @Override - public void execute() { - logger.info("'" + GetVolumeSnapshotDetailsCmd.class.getSimpleName() + ".execute' method invoked"); - - List responses = util.getSnapshotDetails(snapshotUuid); - - ListResponse listReponse = new ListResponse<>(); - - listReponse.setResponses(responses); - listReponse.setResponseName(getCommandName()); - listReponse.setObjectName("apivolumesnapshotdetails"); - - this.setResponseObject(listReponse); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetVolumeiScsiNameCmd.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetVolumeiScsiNameCmd.java deleted file mode 100644 index e2063ce9d69a..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/command/admin/solidfire/GetVolumeiScsiNameCmd.java +++ /dev/null @@ -1,60 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.command.admin.solidfire; - -import javax.inject.Inject; - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.BaseCmd; -import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.solidfire.ApiVolumeiScsiNameResponse; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; - -@APICommand(name = "getVolumeiScsiName", responseObject = ApiVolumeiScsiNameResponse.class, description = "Get Volume's iSCSI Name", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) - -public class GetVolumeiScsiNameCmd extends BaseCmd { - - @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, description = "CloudStack Volume UUID", required = true) - private String volumeUuid; - - @Inject private SolidFireIntegrationTestUtil _util; - - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - - @Override - public long getEntityOwnerId() { - return _util.getAccountIdForVolumeUuid(volumeUuid); - } - - @Override - public void execute() { - logger.info("'GetVolumeiScsiNameCmd.execute' method invoked"); - - String volume_iScsiName = _util.getVolume_iScsiName(volumeUuid); - - ApiVolumeiScsiNameResponse response = new ApiVolumeiScsiNameResponse(volume_iScsiName); - - response.setResponseName(getCommandName()); - response.setObjectName("apivolumeiscsiname"); - - this.setResponseObject(response); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiPathForVolumeResponse.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiPathForVolumeResponse.java deleted file mode 100644 index 3e0f820aeab7..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiPathForVolumeResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response.solidfire; - -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponse; - -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; - -public class ApiPathForVolumeResponse extends BaseResponse { - @SerializedName(ApiConstants.PATH) - @Param(description = "The path field for the volume") - private String path; - - public ApiPathForVolumeResponse(String path) { - this.path = path; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireAccountIdResponse.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireAccountIdResponse.java deleted file mode 100644 index a1c2a4c32d18..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireAccountIdResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response.solidfire; - -import com.cloud.serializer.Param; - -import com.google.gson.annotations.SerializedName; - -import org.apache.cloudstack.api.BaseResponse; - -public class ApiSolidFireAccountIdResponse extends BaseResponse { - @SerializedName("solidFireAccountId") - @Param(description = "SolidFire Account ID") - private long solidFireAccountId; - - public ApiSolidFireAccountIdResponse(long sfAccountId) { - solidFireAccountId = sfAccountId; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireVolumeAccessGroupIdsResponse.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireVolumeAccessGroupIdsResponse.java deleted file mode 100644 index 299b3bdd4c87..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireVolumeAccessGroupIdsResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response.solidfire; - -import com.cloud.serializer.Param; - -import com.google.gson.annotations.SerializedName; - -import org.apache.cloudstack.api.BaseResponse; - -public class ApiSolidFireVolumeAccessGroupIdsResponse extends BaseResponse { - @SerializedName("solidFireVolumeAccessGroupIds") - @Param(description = "SolidFire Volume Access Group Ids") - private long[] solidFireVolumeAccessGroupIds; - - public ApiSolidFireVolumeAccessGroupIdsResponse(long[] sfVolumeAccessGroupIds) { - solidFireVolumeAccessGroupIds = sfVolumeAccessGroupIds; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireVolumeSizeResponse.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireVolumeSizeResponse.java deleted file mode 100644 index 9ae89d52310e..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiSolidFireVolumeSizeResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response.solidfire; - -import com.cloud.serializer.Param; - -import com.google.gson.annotations.SerializedName; - -import org.apache.cloudstack.api.BaseResponse; - -public class ApiSolidFireVolumeSizeResponse extends BaseResponse { - @SerializedName("solidFireVolumeSize") - @Param(description = "SolidFire Volume Size Including Hypervisor Snapshot Reserve") - private long solidFireVolumeSize; - - public ApiSolidFireVolumeSizeResponse(long sfVolumeSize) { - solidFireVolumeSize = sfVolumeSize; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiVolumeSnapshotDetailsResponse.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiVolumeSnapshotDetailsResponse.java deleted file mode 100644 index 914fa1f1b0e9..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiVolumeSnapshotDetailsResponse.java +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response.solidfire; - -import com.cloud.serializer.Param; - -import com.google.gson.annotations.SerializedName; - -import org.apache.cloudstack.api.BaseResponse; - -public class ApiVolumeSnapshotDetailsResponse extends BaseResponse { - @SerializedName("volumeSnapshotId") - @Param(description = "CloudStack Volume Snapshot ID") - private long volumeSnapshotId; - - @SerializedName("snapshotDetailsName") - @Param(description = "Snapshot Details Name") - private String volumeSnapshotDetailsName; - - @SerializedName("snapshotDetailsValue") - @Param(description = "Snapshot Details Value") - private String volumeSnapshotDetailsValue; - - public ApiVolumeSnapshotDetailsResponse(long volumeSnapshotId, String volumeSnapshotDetailsName, String volumeSnapshotDetailsValue) { - this.volumeSnapshotId = volumeSnapshotId; - this.volumeSnapshotDetailsName = volumeSnapshotDetailsName; - this.volumeSnapshotDetailsValue = volumeSnapshotDetailsValue; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiVolumeiScsiNameResponse.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiVolumeiScsiNameResponse.java deleted file mode 100644 index f43e53352aaa..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/response/solidfire/ApiVolumeiScsiNameResponse.java +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.response.solidfire; - -import com.cloud.serializer.Param; - -import com.google.gson.annotations.SerializedName; - -import org.apache.cloudstack.api.BaseResponse; - -public class ApiVolumeiScsiNameResponse extends BaseResponse { - @SerializedName("volumeiScsiName") - @Param(description = "Volume iSCSI Name") - private String volumeiScsiName; - - public ApiVolumeiScsiNameResponse(String volumeiScsiName) { - this.volumeiScsiName = volumeiScsiName; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/solidfire/ApiSolidFireIntegrationTestService.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/solidfire/ApiSolidFireIntegrationTestService.java deleted file mode 100644 index ff206d3a3b6a..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/solidfire/ApiSolidFireIntegrationTestService.java +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.solidfire; - -import com.cloud.utils.component.PluggableService; - -public interface ApiSolidFireIntegrationTestService extends PluggableService { -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/solidfire/ApiSolidFireIntegrationTestServiceImpl.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/solidfire/ApiSolidFireIntegrationTestServiceImpl.java deleted file mode 100644 index 91868f4ef8f0..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/api/solidfire/ApiSolidFireIntegrationTestServiceImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.api.solidfire; - -import java.util.List; -import java.util.ArrayList; - -import org.apache.cloudstack.api.command.admin.solidfire.GetPathForVolumeCmd; -import org.apache.cloudstack.api.command.admin.solidfire.GetSolidFireAccountIdCmd; -import org.apache.cloudstack.api.command.admin.solidfire.GetSolidFireVolumeAccessGroupIdsCmd; -import org.apache.cloudstack.api.command.admin.solidfire.GetVolumeSnapshotDetailsCmd; -import org.apache.cloudstack.api.command.admin.solidfire.GetVolumeiScsiNameCmd; -import org.apache.cloudstack.api.command.admin.solidfire.GetSolidFireVolumeSizeCmd; -import org.springframework.stereotype.Component; - -import com.cloud.utils.component.AdapterBase; - -@Component -public class ApiSolidFireIntegrationTestServiceImpl extends AdapterBase implements ApiSolidFireIntegrationTestService { - @Override - public List> getCommands() { - List> cmdList = new ArrayList>(); - - cmdList.add(GetPathForVolumeCmd.class); - cmdList.add(GetSolidFireAccountIdCmd.class); - cmdList.add(GetSolidFireVolumeAccessGroupIdsCmd.class); - cmdList.add(GetVolumeiScsiNameCmd.class); - cmdList.add(GetSolidFireVolumeSizeCmd.class); - cmdList.add(GetVolumeSnapshotDetailsCmd.class); - - return cmdList; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManager.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManager.java deleted file mode 100644 index 302a034911f1..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManager.java +++ /dev/null @@ -1,23 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.solidfire; - -public interface SolidFireIntegrationTestManager { - long getSolidFireAccountId(String csAccountUuid, String storagePoolUuid); - long[] getSolidFireVolumeAccessGroupIds(String csClusterUuid, String storagePoolUuid); - long getSolidFireVolumeSize(String volumeUuid); -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java deleted file mode 100644 index 0339379d116a..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/solidfire/SolidFireIntegrationTestManagerImpl.java +++ /dev/null @@ -1,107 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.solidfire; - -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; -import com.cloud.storage.VolumeDetailVO; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.storage.dao.VolumeDetailsDao; -import com.cloud.user.AccountDetailVO; -import com.cloud.user.AccountDetailsDao; -import com.cloud.utils.exception.CloudRuntimeException; - -import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; -import org.apache.cloudstack.storage.datastore.util.SolidFireUtil; -import org.apache.cloudstack.util.solidfire.SolidFireIntegrationTestUtil; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import javax.inject.Inject; - -@Component -public class SolidFireIntegrationTestManagerImpl implements SolidFireIntegrationTestManager { - @Inject private AccountDetailsDao accountDetailsDao; - @Inject private HostDao hostDao; - @Inject private SolidFireIntegrationTestUtil util; - @Inject private StoragePoolDetailsDao storagePoolDetailsDao; - @Inject private VolumeDao volumeDao; - @Inject private VolumeDetailsDao volumeDetailsDao; - - @Override - public long getSolidFireAccountId(String csAccountUuid, String storagePoolUuid) { - long csAccountId = util.getAccountIdForAccountUuid(csAccountUuid); - long storagePoolId = util.getStoragePoolIdForStoragePoolUuid(storagePoolUuid); - - AccountDetailVO accountDetail = accountDetailsDao.findDetail(csAccountId, SolidFireUtil.getAccountKey(storagePoolId)); - - if (accountDetail == null) { - throw new CloudRuntimeException("Unable to find SF account for storage " + storagePoolUuid + " for CS account " + csAccountUuid); - } - - String sfAccountId = accountDetail.getValue(); - - return Long.parseLong(sfAccountId); - } - - @Override - public long[] getSolidFireVolumeAccessGroupIds(String csClusterUuid, String storagePoolUuid) { - long storagePoolId = util.getStoragePoolIdForStoragePoolUuid(storagePoolUuid); - - SolidFireUtil.SolidFireConnection sfConnection = SolidFireUtil.getSolidFireConnection(storagePoolId, storagePoolDetailsDao); - - List sfVags = SolidFireUtil.getAllVags(sfConnection); - - long csClusterId = util.getClusterIdForClusterUuid(csClusterUuid); - List hosts = hostDao.findByClusterId(csClusterId); - - if (hosts == null) { - return new long[0]; - } - - List vagIds = new ArrayList<>(hosts.size()); - - for (HostVO host : hosts) { - String iqn = host.getStorageUrl(); - - SolidFireUtil.SolidFireVag sfVag = SolidFireUtil.getVolumeAccessGroup(iqn, sfVags); - - if (sfVag != null) { - if (!vagIds.contains(sfVag.getId())) { - vagIds.add(sfVag.getId()); - } - } - } - - return vagIds.stream().mapToLong(l -> l).toArray(); - } - - @Override - public long getSolidFireVolumeSize(String volumeUuid) { - VolumeVO volume = volumeDao.findByUuid(volumeUuid); - - VolumeDetailVO volumeDetail = volumeDetailsDao.findDetail(volume.getId(), SolidFireUtil.VOLUME_SIZE); - - if (volumeDetail != null && volumeDetail.getValue() != null) { - return Long.parseLong(volumeDetail.getValue()); - } - - throw new CloudRuntimeException("Unable to determine the size of the SolidFire volume"); - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java b/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java deleted file mode 100644 index 4cbf74aba675..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/java/org/apache/cloudstack/util/solidfire/SolidFireIntegrationTestUtil.java +++ /dev/null @@ -1,145 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. -package org.apache.cloudstack.util.solidfire; - -import com.cloud.dc.ClusterVO; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.storage.SnapshotVO; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.dao.SnapshotDao; -import com.cloud.storage.dao.SnapshotDetailsDao; -import com.cloud.storage.dao.SnapshotDetailsVO; -import com.cloud.storage.dao.VolumeDao; -import com.cloud.user.Account; -import com.cloud.user.dao.AccountDao; -import com.cloud.utils.exception.CloudRuntimeException; - -import org.apache.cloudstack.api.response.solidfire.ApiVolumeSnapshotDetailsResponse; -import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - -public class SolidFireIntegrationTestUtil { - @Inject private AccountDao accountDao; - @Inject private ClusterDao clusterDao; - @Inject private PrimaryDataStoreDao storagePoolDao; - @Inject private SnapshotDao snapshotDao; - @Inject private SnapshotDetailsDao snapshotDetailsDao; - @Inject private VolumeDao volumeDao; - - private SolidFireIntegrationTestUtil() {} - - public long getAccountIdForAccountUuid(String accountUuid) { - Account account = accountDao.findByUuid(accountUuid); - - if (account == null) { - throw new CloudRuntimeException("Unable to find Account for ID: " + accountUuid); - } - - return account.getAccountId(); - } - - public long getAccountIdForVolumeUuid(String volumeUuid) { - VolumeVO volume = volumeDao.findByUuid(volumeUuid); - - if (volume == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + volumeUuid); - } - - return volume.getAccountId(); - } - - public long getAccountIdForSnapshotUuid(String snapshotUuid) { - SnapshotVO snapshot = snapshotDao.findByUuid(snapshotUuid); - - if (snapshot == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + snapshotUuid); - } - - return snapshot.getAccountId(); - } - - public long getClusterIdForClusterUuid(String clusterUuid) { - ClusterVO cluster = clusterDao.findByUuid(clusterUuid); - - if (cluster == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + clusterUuid); - } - - return cluster.getId(); - } - - public long getStoragePoolIdForStoragePoolUuid(String storagePoolUuid) { - StoragePoolVO storagePool = storagePoolDao.findByUuid(storagePoolUuid); - - if (storagePool == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + storagePoolUuid); - } - - return storagePool.getId(); - } - - public String getPathForVolumeUuid(String volumeUuid) { - VolumeVO volume = volumeDao.findByUuid(volumeUuid); - - if (volume == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + volumeUuid); - } - - return volume.getPath(); - } - - public String getVolume_iScsiName(String volumeUuid) { - VolumeVO volume = volumeDao.findByUuid(volumeUuid); - - if (volume == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + volumeUuid); - } - - return volume.get_iScsiName(); - } - - public List getSnapshotDetails(String snapshotUuid) { - SnapshotVO snapshot = snapshotDao.findByUuid(snapshotUuid); - - if (snapshot == null) { - throw new CloudRuntimeException("Unable to find Volume for ID: " + snapshotUuid); - } - - List snapshotDetails = snapshotDetailsDao.listDetails(snapshot.getId()); - - List responses = new ArrayList<>(); - - if (snapshotDetails != null) { - for (SnapshotDetailsVO snapshotDetail : snapshotDetails) { - ApiVolumeSnapshotDetailsResponse response = new ApiVolumeSnapshotDetailsResponse( - snapshotDetail.getResourceId(), - snapshotDetail.getName(), - snapshotDetail.getValue() - ); - - responses.add(response); - } - } - - return responses; - } -} diff --git a/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties b/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties deleted file mode 100644 index 3b3f2a816770..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/module.properties +++ /dev/null @@ -1,18 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -name=solidfire-intg-test -parent=api diff --git a/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/spring-solidfire-intg-test-context.xml b/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/spring-solidfire-intg-test-context.xml deleted file mode 100644 index 28d1e1a9024c..000000000000 --- a/plugins/api/solidfire-intg-test/src/main/resources/META-INF/cloudstack/solidfire-intg-test/spring-solidfire-intg-test-context.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - diff --git a/plugins/api/vmware-sioc/pom.xml b/plugins/api/vmware-sioc/pom.xml index e523568dc506..069196e39351 100644 --- a/plugins/api/vmware-sioc/pom.xml +++ b/plugins/api/vmware-sioc/pom.xml @@ -46,7 +46,7 @@ org.apache.maven.plugins maven-surefire-plugin - @{argLine} -Xmx1024m + @{argLine} -javaagent:${org.mockito:mockito-core:jar} -Xmx1024m diff --git a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java index b9dd659905b0..d790384613e8 100644 --- a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java +++ b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/api/command/admin/sioc/UpdateSiocInfoCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.sioc; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java index b01af35725f9..1079f7a07cc2 100644 --- a/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java +++ b/plugins/api/vmware-sioc/src/main/java/org/apache/cloudstack/sioc/SiocManagerImpl.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; diff --git a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java index b228a9f8ce05..37497ab81670 100644 --- a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.offering.DiskOffering; import com.cloud.storage.StoragePoolHostVO; diff --git a/plugins/backup/nas/pom.xml b/plugins/backup/nas/pom.xml index 3c7cd8ab681f..c3bd66382fd5 100644 --- a/plugins/backup/nas/pom.xml +++ b/plugins/backup/nas/pom.xml @@ -45,8 +45,8 @@ ${cs.jackson.version} - com.github.tomakehurst - wiremock-standalone + org.wiremock + wiremock ${cs.wiremock.version} test diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index df9336026f4d..3f0b37d7dd78 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -60,7 +60,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; diff --git a/plugins/backup/networker/pom.xml b/plugins/backup/networker/pom.xml index cad2b3454355..9f4d1e6f263b 100644 --- a/plugins/backup/networker/pom.xml +++ b/plugins/backup/networker/pom.xml @@ -45,8 +45,8 @@ ${cs.jackson.version} - com.github.tomakehurst - wiremock-standalone + org.wiremock + wiremock ${cs.wiremock.version} test diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java index 4cf4bd111ef1..91f3da5664a2 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java @@ -51,10 +51,11 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import org.apache.xml.utils.URI; import org.apache.cloudstack.backup.networker.api.NetworkerBackup; -import javax.inject.Inject; +import java.net.URI; + +import jakarta.inject.Inject; import java.net.URISyntaxException; import java.security.KeyManagementException; @@ -139,14 +140,11 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid private AgentManager agentMgr; private static String getUrlDomain(String url) throws URISyntaxException { - URI uri; try { - uri = new URI(url); - } catch (URI.MalformedURIException e) { + return new URI(url).getHost(); + } catch (URISyntaxException e) { throw new CloudRuntimeException("Failed to cast URI"); } - - return uri.getHost(); } @Override diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java index 271fec78188d..8adaf6331a08 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java @@ -46,7 +46,7 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; import java.io.IOException; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Action.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Action.java index 12f55a0e79d7..92974c4754de 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Action.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Action.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ActionSpecificData.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ActionSpecificData.java index a325384036ed..a7e960ed57d2 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ActionSpecificData.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ActionSpecificData.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Attribute.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Attribute.java index 2f7791efdb71..7ade248606a1 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Attribute.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Attribute.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/BackupSpecificData.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/BackupSpecificData.java index 64ed8442e5d6..4c74e4afbd76 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/BackupSpecificData.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/BackupSpecificData.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/CompletionNotification.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/CompletionNotification.java index c461b0dbc6c1..7655b35a65e7 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/CompletionNotification.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/CompletionNotification.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Expire.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Expire.java index 03a3aa3518b4..b01c1560248f 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Expire.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Expire.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Instance.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Instance.java index 8b6eb1456a9f..3a22967636a4 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Instance.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Instance.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Link.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Link.java index f4b8d7f38c24..e22f45de5d94 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Link.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Link.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackup.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackup.java index 350e520f0f2a..e3b07e443ec3 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackup.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackup.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackups.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackups.java index 261fbf814587..7abc8aff4d31 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackups.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/NetworkerBackups.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicies.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicies.java index a572ba1b15f8..104a1da2933e 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicies.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicies.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicy.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicy.java index e39a149b33ce..b124beec2f8e 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicy.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ProtectionPolicy.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; import java.util.List; diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ResourceId.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ResourceId.java index edf8c1a8fe78..32bb54fa25aa 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ResourceId.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ResourceId.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ServerBackup.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ServerBackup.java index 12adb14093cd..75f87dd79dd7 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ServerBackup.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/ServerBackup.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Size.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Size.java index 826989714b61..e706258b1376 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Size.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Size.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/SummaryNotification.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/SummaryNotification.java index e76b6cfe9a50..895240a90ec6 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/SummaryNotification.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/SummaryNotification.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Traditional.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Traditional.java index 8768d9d33b95..4a4725b8886b 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Traditional.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/api/Traditional.java @@ -21,7 +21,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.annotation.Generated; +import jakarta.annotation.Generated; import java.io.Serializable; @JsonInclude(JsonInclude.Include.NON_NULL) diff --git a/plugins/backup/veeam/pom.xml b/plugins/backup/veeam/pom.xml index a2dcbd1d3487..3342e51a62da 100644 --- a/plugins/backup/veeam/pom.xml +++ b/plugins/backup/veeam/pom.xml @@ -59,8 +59,8 @@ ${cs.commons-lang3.version} - com.github.tomakehurst - wiremock-standalone + org.wiremock + wiremock ${cs.wiremock.version} test diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java index 39970dab3427..65f6c3037f82 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.backup.veeam.VeeamClient; diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java index 25c45ed2a102..8de67538d5f6 100644 --- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java +++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java @@ -46,14 +46,14 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import javax.xml.bind.DatatypeConverter; +import jakarta.xml.bind.DatatypeConverter; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.framework.ca.CAProvider; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java index 0cec0df66182..db8ade5e0ea0 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaBalanceCmd.java @@ -19,7 +19,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.user.Account; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java index f658783179fb..287e43789ef0 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaConfigureEmailCmd.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.api.response.QuotaResponseBuilder; import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaConfigureEmail", responseObject = QuotaConfigureEmailResponse.class, description = "Configure a quota email template", since = "4.20.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsCmd.java index a6d1db41ddd2..9fcfc32dff68 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsCmd.java @@ -31,7 +31,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.quota.QuotaService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaCredits", responseObject = QuotaCreditsResponse.class, description = "Add +-credits to an Account", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaCreditsCmd extends BaseCmd { diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java index 48bb7ef79e70..3c5efbe0d3ff 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaCreditsListCmd.java @@ -35,7 +35,7 @@ import java.util.Date; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaCreditsList", responseObject = QuotaCreditsResponse.class, description = "Lists quota credits of an account.", since = "4.21.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java index db274851382b..e1c5cbb38eff 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateListCmd.java @@ -23,7 +23,7 @@ import org.apache.cloudstack.api.response.QuotaEmailTemplateResponse; import org.apache.cloudstack.api.response.QuotaResponseBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaEmailTemplateList", responseObject = QuotaEmailTemplateResponse.class, description = "Lists all quota email Templates", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaEmailTemplateListCmd extends BaseListCmd { diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java index eb9562e8a391..2adf029c4105 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEmailTemplateUpdateCmd.java @@ -26,7 +26,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.quota.constant.QuotaConfig; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Arrays; @APICommand(name = "quotaEmailTemplateUpdate", responseObject = SuccessResponse.class, description = "Updates existing email Templates for quota alerts", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEnabledCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEnabledCmd.java index af1d146ea9dc..be48a281c131 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEnabledCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaEnabledCmd.java @@ -24,7 +24,7 @@ import org.apache.cloudstack.quota.QuotaService; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaIsEnabled", responseObject = QuotaEnabledResponse.class, description = "Return true if the plugin is enabled", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, httpMethod = "GET") diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java index 294738a7b997..d19ed883968b 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaListEmailConfigurationCmd.java @@ -26,7 +26,7 @@ import org.apache.cloudstack.api.response.QuotaConfigureEmailResponse; import org.apache.cloudstack.api.response.QuotaResponseBuilder; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaListEmailConfiguration", responseObject = QuotaConfigureEmailResponse.class, description = "List quota email template configurations", since = "4.20.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaPresetVariablesListCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaPresetVariablesListCmd.java index 8de16dd2741e..5d4cf775710f 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaPresetVariablesListCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaPresetVariablesListCmd.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.api.response.QuotaResponseBuilder; import org.apache.cloudstack.quota.constant.QuotaTypes; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; @APICommand(name = "quotaPresetVariablesList", responseObject = QuotaPresetVariablesItemResponse.class, description = "List the preset variables available for using in the " + diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java index bfe26a9f4250..43596159a59f 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaStatementCmd.java @@ -18,7 +18,7 @@ import java.util.Date; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java index 870b9b6df6e5..3b336385036b 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaSummaryCmd.java @@ -36,7 +36,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaSummary", responseObject = QuotaSummaryResponse.class, description = "Lists Quota balance summary of Accounts and Projects.", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, httpMethod = "GET") diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java index f1fd4b4afe12..f6009bb0c15c 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffCreateCmd.java @@ -31,7 +31,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.quota.vo.QuotaTariffVO; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffDeleteCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffDeleteCmd.java index a5d588c20c32..33b25ce26e26 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffDeleteCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffDeleteCmd.java @@ -30,7 +30,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaTariffDelete", description = "Marks a quota tariff as removed.", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.18.0.0", authorized = {RoleType.Admin}) diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java index e0bab07501b6..9b99944a511a 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffListCmd.java @@ -32,7 +32,7 @@ import org.apache.cloudstack.quota.vo.QuotaTariffVO; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.Date; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java index b57668755075..9e79764ad149 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaTariffUpdateCmd.java @@ -31,7 +31,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.quota.vo.QuotaTariffVO; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaUpdateCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaUpdateCmd.java index 986b2d4ce981..c377e67e8f6f 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaUpdateCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaUpdateCmd.java @@ -27,7 +27,7 @@ import java.util.Calendar; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaUpdate", responseObject = QuotaUpdateResponse.class, description = "Update quota calculations, alerts and statements", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaUpdateCmd extends BaseCmd { diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaValidateActivationRuleCmd.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaValidateActivationRuleCmd.java index a9dc7ea63eb1..05b65a9ff76d 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaValidateActivationRuleCmd.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/command/QuotaValidateActivationRuleCmd.java @@ -26,7 +26,7 @@ import org.apache.cloudstack.api.response.QuotaValidateActivationRuleResponse; import org.apache.cloudstack.quota.constant.QuotaTypes; -import javax.inject.Inject; +import jakarta.inject.Inject; @APICommand(name = "quotaValidateActivationRule", responseObject = QuotaValidateActivationRuleResponse.class, description = "Validates if the given activation rule is valid for the informed usage type.", since = "4.20.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class QuotaValidateActivationRuleCmd extends BaseCmd { diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java index c919bb5887c1..94039dea45f7 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImpl.java @@ -39,7 +39,7 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.domain.Domain; import com.cloud.domain.DomainVO; diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java index a0ba2fbc751d..9efa78c49b50 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaServiceImpl.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.TimeZone; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.projects.ProjectManager; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java index 049a0227f359..0a03d10f2d22 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateClusterCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java index 0a953357cc1e..3ad365cf5d7d 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateHostCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java index 6cd997a85065..7e97c85bd24d 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicatePodCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java index 48e98ac24158..fc324c2025e2 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/DedicateZoneCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java index 10f51a91d7a3..dad04983a84c 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedClustersCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupResponse; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java index 2011ca8b8496..b76e6d2414be 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedHostsCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupResponse; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java index e8a8d7b67cc7..e9bec614402d 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedPodsCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupResponse; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java index cabad388d4ed..92c7f7f2fcae 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ListDedicatedZonesCmd.java @@ -19,7 +19,7 @@ import java.util.ArrayList; import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupResponse; diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java index 2b51f02ea248..00d925410a38 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedClusterCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.commands; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; @@ -67,8 +67,7 @@ public long getEntityOwnerId() { public void execute() { boolean result = dedicatedService.releaseDedicatedResource(null, null, getClusterId(), null); if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - this.setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated cluster"); } diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java index 199eb65c13c6..a462608e41f7 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedHostCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.commands; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; @@ -67,8 +67,7 @@ public long getEntityOwnerId() { public void execute() { boolean result = dedicatedService.releaseDedicatedResource(null, null, null, getHostId()); if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - this.setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated Host"); } diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java index 0aad33264170..75ad2a0929d5 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedPodCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.commands; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; @@ -67,8 +67,7 @@ public long getEntityOwnerId() { public void execute() { boolean result = dedicatedService.releaseDedicatedResource(null, getPodId(), null, null); if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - this.setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated pod"); } diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java index ba6902deeb1f..d99824a77e30 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/api/commands/ReleaseDedicatedZoneCmd.java @@ -16,7 +16,7 @@ // under the License. package org.apache.cloudstack.api.commands; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.APICommand; @@ -67,8 +67,7 @@ public long getEntityOwnerId() { public void execute() { boolean result = dedicatedService.releaseDedicatedResource(getZoneId(), null, null, null); if (result) { - SuccessResponse response = new SuccessResponse(getCommandName()); - this.setResponseObject(response); + setSuccessResponse(); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to release dedicated zone"); } diff --git a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java index 1f0207267932..831d0c40310f 100644 --- a/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java +++ b/plugins/dedicated-resources/src/main/java/org/apache/cloudstack/dedicated/DedicatedResourceManagerImpl.java @@ -20,7 +20,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.affinity.AffinityGroup; diff --git a/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java b/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java index c13b8b12d9da..8531796f8f3e 100644 --- a/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java +++ b/plugins/dedicated-resources/src/test/java/org/apache/cloudstack/dedicated/manager/DedicatedApiUnitTest.java @@ -24,7 +24,7 @@ import java.io.IOException; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.affinity.dao.AffinityGroupDao; diff --git a/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java b/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java index f9cde2ae4414..deefcf07afcd 100644 --- a/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java +++ b/plugins/deployment-planners/implicit-dedication/src/main/java/com/cloud/deploy/ImplicitDedicationPlanner.java @@ -23,7 +23,7 @@ import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.commons.collections.CollectionUtils; diff --git a/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java b/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java index d859ebd0ffba..a2f1cdd18a50 100644 --- a/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java +++ b/plugins/deployment-planners/implicit-dedication/src/test/java/org/apache/cloudstack/implicitplanner/ImplicitPlannerTest.java @@ -34,7 +34,7 @@ import java.util.Set; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java index a484f29e8d29..8f0fe58aaa93 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookApiServiceImpl.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.ApiConstants; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookEventBus.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookEventBus.java index c2dade843618..c8b562d96fa1 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookEventBus.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookEventBus.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.framework.events.Event; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java index 624de54d41bb..4a5c27091d36 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookServiceImpl.java @@ -29,7 +29,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.acl.ControlledEntity; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/AddWebhookFilterCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/AddWebhookFilterCmd.java index ba71cc1a2e83..a0292c7703cb 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/AddWebhookFilterCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/AddWebhookFilterCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/CreateWebhookCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/CreateWebhookCmd.java index 12da15b3d673..a54225547ec2 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/CreateWebhookCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/CreateWebhookCmd.java @@ -18,7 +18,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookCmd.java index c9fb01580c2d..406db2b4f5f0 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookDeliveryCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookDeliveryCmd.java index dcfe71bf1713..0ff9690ff00d 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookDeliveryCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookDeliveryCmd.java @@ -19,7 +19,7 @@ import java.util.Date; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookFilterCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookFilterCmd.java index 80812c9b230b..b64183b09a07 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookFilterCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/DeleteWebhookFilterCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ExecuteWebhookDeliveryCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ExecuteWebhookDeliveryCmd.java index c3dfe8500531..0310b45fdb9e 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ExecuteWebhookDeliveryCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ExecuteWebhookDeliveryCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookDeliveriesCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookDeliveriesCmd.java index cf9c046b2b22..ddb88680b012 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookDeliveriesCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookDeliveriesCmd.java @@ -19,7 +19,7 @@ import java.util.Date; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookFiltersCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookFiltersCmd.java index 1641ea674bbc..561c89ff9b8c 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookFiltersCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhookFiltersCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhooksCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhooksCmd.java index 2719e0aaa991..00cb289f5d81 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhooksCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/ListWebhooksCmd.java @@ -18,7 +18,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/UpdateWebhookCmd.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/UpdateWebhookCmd.java index e27fe1a79191..10f4cb522dde 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/UpdateWebhookCmd.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/api/command/user/UpdateWebhookCmd.java @@ -17,7 +17,7 @@ package org.apache.cloudstack.mom.webhook.api.command.user; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryJoinVO.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryJoinVO.java index f0fb3e1cc9b1..285a6d805e58 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryJoinVO.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryJoinVO.java @@ -20,14 +20,14 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryVO.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryVO.java index e266ea5d7c4b..986958c312c4 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryVO.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookDeliveryVO.java @@ -21,14 +21,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Temporal; +import jakarta.persistence.TemporalType; import org.apache.cloudstack.mom.webhook.WebhookDelivery; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookFilterVO.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookFilterVO.java index 75e18f19516e..f89e3a6249cc 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookFilterVO.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookFilterVO.java @@ -20,14 +20,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.mom.webhook.WebhookFilter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookJoinVO.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookJoinVO.java index 9ff15d34a9cd..f7f3fd4aaee6 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookJoinVO.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookJoinVO.java @@ -20,12 +20,12 @@ import java.util.Date; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.mom.webhook.Webhook; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookVO.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookVO.java index 852cdf740d1a..586167190866 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookVO.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/vo/WebhookVO.java @@ -21,14 +21,14 @@ import java.util.Date; import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import org.apache.cloudstack.mom.webhook.Webhook; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; diff --git a/plugins/hypervisors/baremetal/pom.xml b/plugins/hypervisors/baremetal/pom.xml index fc4803b79029..1afe176a6f4b 100755 --- a/plugins/hypervisors/baremetal/pom.xml +++ b/plugins/hypervisors/baremetal/pom.xml @@ -33,18 +33,18 @@ commons-lang - javax.xml.bind - jaxb-api + jakarta.xml.bind + jakarta.xml.bind-api ${cs.jaxb.version} - com.sun.xml.bind + org.glassfish.jaxb jaxb-core ${cs.jaxb.version} - com.sun.xml.bind - jaxb-impl + org.glassfish.jaxb + jaxb-runtime ${cs.jaxb.impl.version} diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpVO.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpVO.java index 6e3e167d8bce..0906f0c5d743 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpVO.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalDhcpVO.java @@ -20,12 +20,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "baremetal_dhcp_devices") diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeVO.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeVO.java index 10028961c3bb..a020e0230d28 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeVO.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalPxeVO.java @@ -20,12 +20,12 @@ import java.util.UUID; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; @Entity @Table(name = "baremetal_pxe_devices") diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java index fdee9e6e2e11..79430d8e271f 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/database/BaremetalRctVO.java @@ -20,12 +20,12 @@ import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Table; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.util.UUID; /** diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java index f12d701d2e29..cbb4e2d6f393 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalDiscoverer.java @@ -29,7 +29,7 @@ import java.util.Map; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.api.ApiConstants; @@ -164,7 +164,7 @@ public Map> find(long dcId, Long p BareMetalResourceBase resource = null; if (resourceClassName != null) { Class clazz = Class.forName(resourceClassName); - resource = (BareMetalResourceBase) clazz.newInstance(); + resource = (BareMetalResourceBase) clazz.getDeclaredConstructor().newInstance(); String externalUrl = _configDao.getValue(Config.ExternalBaremetalSystemUrl.key()); if (externalUrl == null) { throw new IllegalArgumentException(String.format("You must specify ExternalBaremetalSystemUrl in global config page as ExternalBaremetalResourceClassName is not null")); diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java index a1b306b66c91..9b9b55e687fb 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalGuru.java @@ -25,7 +25,7 @@ import java.util.HashMap; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.agent.api.to.VirtualMachineTO; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java index 83199b5f51c0..b2770bdb1ebb 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalPlanner.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.NumbersUtil; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java index c6c38a398098..945e03c73b97 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BareMetalTemplateAdapter.java @@ -46,7 +46,7 @@ import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Date; import java.util.List; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java index d90ea6c37312..fa05adc8485b 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalManagerImpl.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import com.cloud.utils.db.QueryBuilder; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java index c05d52326cea..33ef6f127c3e 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/manager/BaremetalVlanManagerImpl.java @@ -47,7 +47,7 @@ import org.apache.cloudstack.utils.baremetal.BaremetalUtils; import org.springframework.web.client.RestTemplate; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java index 509fd340dae4..ea8823d58cc3 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPingServiceImpl.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.AddBaremetalPxeCmd; import org.apache.cloudstack.api.AddBaremetalPxePingServerCmd; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPxeServiceBase.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPxeServiceBase.java index 842f9c3f2695..a97cb081fc20 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPxeServiceBase.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BareMetalPxeServiceBase.java @@ -22,7 +22,7 @@ // Automatically generated by addcopyright.py at 04/03/2012 package com.cloud.baremetal.networkservice; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.agent.AgentManager; import com.cloud.dc.dao.DataCenterDao; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java index 79590f08ffa4..223bfeeea139 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetaNetworkGuru.java @@ -18,7 +18,7 @@ // Automatically generated by addcopyright.py at 01/29/2013 package com.cloud.baremetal.networkservice; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java index e39b40cfc68b..3710bcdfff5d 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpElement.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.baremetal.database.BaremetalDhcpVO; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java index 9bdc2fb9ed86..fd6b1531b247 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalDhcpManagerImpl.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.api.AddBaremetalDhcpCmd; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java index 6e1f422526d9..2475e6c27ec2 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalKickStartServiceImpl.java @@ -27,7 +27,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.AddBaremetalKickStartPxeCmd; import org.apache.cloudstack.api.AddBaremetalPxeCmd; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java index fa708e7be4cc..342140f9a5e7 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeElement.java @@ -52,7 +52,7 @@ import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.VMInstanceDao; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.HashMap; import java.util.Map; import java.util.Set; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java index 3a2384a03c85..172ddff84897 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalPxeManagerImpl.java @@ -26,7 +26,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.api.AddBaremetalKickStartPxeCmd; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java index d2c9731ddd15..a2d4087a4462 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/BaremetalUserdataElement.java @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Set; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.dc.DataCenter.NetworkType; import com.cloud.deploy.DeployDestination; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java index b00535004640..771415bcc389 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/SecurityGroupHttpClient.java @@ -37,8 +37,8 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.Marshaller; +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.Marshaller; import java.io.StringWriter; import java.net.SocketTimeoutException; import java.util.ArrayList; diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/ObjectFactory.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/ObjectFactory.java index a80625bb7c5e..6c8cdf885777 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/ObjectFactory.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/ObjectFactory.java @@ -17,7 +17,7 @@ // package com.cloud.baremetal.networkservice.schema; -import javax.xml.bind.annotation.XmlRegistry; +import jakarta.xml.bind.annotation.XmlRegistry; /** diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java index 83315714f3d8..9a01c3306702 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupRule.java @@ -19,11 +19,11 @@ import java.util.ArrayList; import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlSchemaType; -import javax.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlSchemaType; +import jakarta.xml.bind.annotation.XmlType; /** diff --git a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java index 2c50c0e24d82..adfb0710bc87 100644 --- a/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java +++ b/plugins/hypervisors/baremetal/src/main/java/com/cloud/baremetal/networkservice/schema/SecurityGroupVmRuleSet.java @@ -10,11 +10,11 @@ import java.util.ArrayList; import java.util.List; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlType; /** diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java index 192b646b150f..19a8b743764e 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalDhcpCmd.java @@ -18,7 +18,7 @@ // Automatically generated by addcopyright.py at 01/29/2013 package org.apache.cloudstack.api; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.response.PhysicalNetworkResponse; diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java index a2c6060a92ff..410e94a025e0 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalPxeCmd.java @@ -18,7 +18,7 @@ // Automatically generated by addcopyright.py at 01/29/2013 package org.apache.cloudstack.api; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.api.response.PhysicalNetworkResponse; diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java index e6307b6a4c3a..20f84b2410db 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/AddBaremetalRctCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.context.CallContext; -import javax.inject.Inject; +import jakarta.inject.Inject; /** * Created by frank on 5/8/14. diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java index a9b166528302..6d6fc9ea1145 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/BaremetalProvisionDoneNotificationCmd.java @@ -27,7 +27,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; -import javax.inject.Inject; +import jakarta.inject.Inject; /** * Created by frank on 9/17/14. diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java index 7b7816557aef..4183f677215b 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/DeleteBaremetalRctCmd.java @@ -29,7 +29,7 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.context.CallContext; -import javax.inject.Inject; +import jakarta.inject.Inject; /** * Created by frank on 10/27/14. diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java index 4c2ca1a61ba3..7e69606b653e 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalDhcpCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PhysicalNetworkResponse; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; @APICommand(name = "listBaremetalDhcp", description = "List baremetal dhcp servers", responseObject = BaremetalDhcpResponse.class, diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java index 9438553bd4da..13c8f216f33a 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalPxeServersCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PhysicalNetworkResponse; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.List; @APICommand(name = "listBaremetalPxeServers", description = "List baremetal pxe server", responseObject = BaremetalPxeResponse.class, diff --git a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java index 691122ea1afd..831ff228d71d 100644 --- a/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java +++ b/plugins/hypervisors/baremetal/src/main/java/org/apache/cloudstack/api/ListBaremetalRctCmd.java @@ -28,7 +28,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.response.ListResponse; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import java.util.List; diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/agent/manager/ExternalServerPlanner.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/agent/manager/ExternalServerPlanner.java index 33da0373b6af..6be0636a6cac 100644 --- a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/agent/manager/ExternalServerPlanner.java +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/agent/manager/ExternalServerPlanner.java @@ -21,7 +21,7 @@ import java.util.Map; import java.util.stream.Collectors; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.extension.Extension; diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java index cd6a2cf996a5..ead7f93bc8a7 100644 --- a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/guru/ExternalHypervisorGuru.java @@ -21,7 +21,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; import org.apache.commons.collections.MapUtils; diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/discoverer/ExternalServerDiscoverer.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/discoverer/ExternalServerDiscoverer.java index fd7b15dc5e5f..33af1071f6a5 100644 --- a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/discoverer/ExternalServerDiscoverer.java +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/discoverer/ExternalServerDiscoverer.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.UUID; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.extension.ExtensionResourceMap; diff --git a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java index fa3f4de50265..6023e2eb90d9 100644 --- a/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java +++ b/plugins/hypervisors/external/src/main/java/org/apache/cloudstack/hypervisor/external/provisioner/ExternalPathPayloadProvisioner.java @@ -45,7 +45,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.api.ApiConstants; diff --git a/plugins/hypervisors/hyperv/src/main/java/com/cloud/ha/HypervInvestigator.java b/plugins/hypervisors/hyperv/src/main/java/com/cloud/ha/HypervInvestigator.java index 4e44d8cb7359..d19a4a636bdc 100644 --- a/plugins/hypervisors/hyperv/src/main/java/com/cloud/ha/HypervInvestigator.java +++ b/plugins/hypervisors/hyperv/src/main/java/com/cloud/ha/HypervInvestigator.java @@ -20,7 +20,7 @@ import java.util.List; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.agent.AgentManager; diff --git a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java index a5fe1442e56e..daf564ed1c5c 100644 --- a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java +++ b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Random; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; diff --git a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/guru/HypervGuru.java b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/guru/HypervGuru.java index d488ee2058fd..acb471ab2ac3 100644 --- a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/guru/HypervGuru.java +++ b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/guru/HypervGuru.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; diff --git a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java index a31637b60deb..154051111ae5 100644 --- a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java +++ b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java @@ -26,7 +26,7 @@ import java.util.Map; import java.util.Random; -import javax.inject.Inject; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; diff --git a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java index 6ad06f426a79..d8a6e9e43566 100644 --- a/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java +++ b/plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java @@ -38,8 +38,8 @@ import java.util.List; import java.util.Map; -import javax.annotation.PostConstruct; -import javax.inject.Inject; +import jakarta.annotation.PostConstruct; +import jakarta.inject.Inject; import javax.naming.ConfigurationException; import org.apache.cloudstack.storage.command.CopyCommand; diff --git a/plugins/hypervisors/hyperv/src/main/java/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java b/plugins/hypervisors/hyperv/src/main/java/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java index 55944a082427..3abaa7b17a99 100644 --- a/plugins/hypervisors/hyperv/src/main/java/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java +++ b/plugins/hypervisors/hyperv/src/main/java/org/apache/cloudstack/storage/motion/HypervStorageMotionStrategy.java @@ -22,7 +22,7 @@ import java.util.List; import java.util.Map; -import javax.inject.Inject; +import jakarta.inject.Inject; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionStrategy; diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml index 255ada09ef4f..f57bdfd30182 100644 --- a/plugins/hypervisors/kvm/pom.xml +++ b/plugins/hypervisors/kvm/pom.xml @@ -29,8 +29,8 @@ - org.codehaus.groovy - groovy-all + org.apache.groovy + groovy ${cs.groovy.version} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java index da9a0d6e2919..fb46795709f3 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/ha/KVMInvestigator.java @@ -35,7 +35,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.Collections; import java.util.List; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/ImageServerControlSocket.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/ImageServerControlSocket.java new file mode 100644 index 000000000000..f87a3a97b4d2 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/ImageServerControlSocket.java @@ -0,0 +1,171 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.hypervisor.kvm.resource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.StandardProtocolFamily; +import java.net.SocketTimeoutException; +import java.net.UnixDomainSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * Communicates with the cloudstack-image-server Unix domain control socket. + */ +public class ImageServerControlSocket { + private static final Logger LOGGER = LogManager.getLogger(ImageServerControlSocket.class); + private static final int CONTROL_SOCKET_TIMEOUT_MILLIS = 5000; + private static final Gson GSON = new GsonBuilder().create(); + + private ImageServerControlSocket() { + } + + static JsonObject sendMessage(String socketPath, Map message) { + String output; + try { + output = sendJson(socketPath, GSON.toJson(message)); + } catch (IOException | RuntimeException e) { + LOGGER.error("Control socket communication failed for socket [{}].", socketPath, e); + return null; + } + if (output == null || output.trim().isEmpty()) { + LOGGER.error("Empty response from control socket"); + return null; + } + + try { + return JsonParser.parseString(output.trim()).getAsJsonObject(); + } catch (Exception e) { + LOGGER.error("Failed to parse control socket response: {}", output, e); + return null; + } + } + + static String sendJson(String socketPath, String json) throws IOException { + UnixDomainSocketAddress socketAddress = UnixDomainSocketAddress.of(socketPath); + try (SocketChannel channel = SocketChannel.open(StandardProtocolFamily.UNIX); + Selector selector = Selector.open()) { + channel.configureBlocking(false); + long deadlineNanos = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(CONTROL_SOCKET_TIMEOUT_MILLIS); + + if (!channel.connect(socketAddress)) { + waitFor(channel, selector, SelectionKey.OP_CONNECT, deadlineNanos); + channel.finishConnect(); + } + + ByteBuffer request = ByteBuffer.wrap((json + "\n").getBytes(StandardCharsets.UTF_8)); + while (request.hasRemaining()) { + if (channel.write(request) == 0) { + waitFor(channel, selector, SelectionKey.OP_WRITE, deadlineNanos); + } + } + channel.shutdownOutput(); + + ByteArrayOutputStream response = new ByteArrayOutputStream(); + ByteBuffer buffer = ByteBuffer.allocate(4096); + while (true) { + int read = channel.read(buffer); + if (read == -1) { + return response.toString(StandardCharsets.UTF_8); + } + if (read == 0) { + waitFor(channel, selector, SelectionKey.OP_READ, deadlineNanos); + continue; + } + buffer.flip(); + while (buffer.hasRemaining()) { + byte current = buffer.get(); + if (current == '\n') { + return response.toString(StandardCharsets.UTF_8); + } + response.write(current); + } + buffer.clear(); + } + } + } + + private static void waitFor(SocketChannel channel, Selector selector, int operation, long deadlineNanos) throws IOException { + long remainingMillis = TimeUnit.NANOSECONDS.toMillis(deadlineNanos - System.nanoTime()); + if (remainingMillis <= 0) { + throw new SocketTimeoutException("Timed out communicating with image server control socket"); + } + + SelectionKey key = channel.keyFor(selector); + if (key == null) { + channel.register(selector, operation); + } else { + key.interestOps(operation); + } + + if (selector.select(remainingMillis) == 0) { + throw new SocketTimeoutException("Timed out communicating with image server control socket"); + } + selector.selectedKeys().clear(); + } + + public static boolean registerTransfer(String socketPath, String transferId, Map config) { + Map message = new HashMap<>(); + message.put("action", "register"); + message.put("transfer_id", transferId); + message.put("config", config); + JsonObject response = sendMessage(socketPath, message); + if (response == null) { + return false; + } + return "ok".equals(response.has("status") ? response.get("status").getAsString() : null); + } + + public static int unregisterTransfer(String socketPath, String transferId) { + Map message = new HashMap<>(); + message.put("action", "unregister"); + message.put("transfer_id", transferId); + JsonObject response = sendMessage(socketPath, message); + if (response == null) { + return -1; + } + if (!"ok".equals(response.has("status") ? response.get("status").getAsString() : null)) { + return -1; + } + return response.has("active_transfers") ? response.get("active_transfers").getAsInt() : -1; + } + + public static boolean isReady(String socketPath) { + Map message = new HashMap<>(); + message.put("action", "status"); + JsonObject response = sendMessage(socketPath, message); + if (response == null) { + return false; + } + return "ok".equals(response.has("status") ? response.get("status").getAsString() : null); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 64ec0ed95d2e..eff2942275bc 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -386,6 +386,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv public static final String CHECKPOINT_DELETE_COMMAND = "virsh checkpoint-delete --domain %s --checkpointname %s --metadata"; + public static final int IMAGE_SERVER_DEFAULT_PORT = 54322; + public static final String IMAGE_SERVER_SYSTEMD_UNIT_NAME = "cloudstack-image-server"; + protected int qcow2DeltaMergeTimeout; private String modifyVlanPath; @@ -399,6 +402,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String heartBeatPath; private String vmActivityCheckPath; private String nasBackupPath; + private String imageServerPath; + private boolean imageServerTlsEnabled = false; + private String imageServerListenAddress; + private String imageServerSocketPath; private String securityGroupPath; private String ovsPvlanDhcpHostPath; private String ovsPvlanVmPath; @@ -813,6 +820,22 @@ public String getNasBackupPath() { return nasBackupPath; } + public String getImageServerPath() { + return imageServerPath; + } + + public boolean isImageServerTlsEnabled() { + return imageServerTlsEnabled; + } + + public String getImageServerListenAddress() { + return imageServerListenAddress; + } + + public String getImageServerSocketPath() { + return imageServerSocketPath; + } + public String getOvsPvlanDhcpHostPath() { return ovsPvlanDhcpHostPath; } @@ -1057,6 +1080,10 @@ public boolean configure(final String name, final Map params) th cachePath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_CACHE_LOCATION); + imageServerTlsEnabled = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_TLS_ENABLED); + imageServerListenAddress = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_LISTEN_ADDRESS); + imageServerSocketPath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_SOCKET_PATH); + params.put("domr.scripts.dir", domrScriptsDir); virtRouterResource = new VirtualRoutingResource(this); @@ -1120,6 +1147,12 @@ public boolean configure(final String name, final Map params) th throw new ConfigurationException("Unable to find nasbackup.sh"); } + String imageServerMain = Script.findScript(kvmScriptsDir, "imageserver/__main__.py"); + if (imageServerMain == null) { + throw new ConfigurationException("Unable to find imageserver package"); + } + imageServerPath = new File(imageServerMain).getParent(); + createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh"); if (createTmplPath == null) { throw new ConfigurationException("Unable to find the createtmplt.sh"); @@ -1815,13 +1848,9 @@ protected VifDriver getVifDriverClass(final String vifDriverClassName, final Map try { final Class clazz = Class.forName(vifDriverClassName); - vifDriver = (VifDriver)clazz.newInstance(); + vifDriver = (VifDriver)clazz.getDeclaredConstructor().newInstance(); vifDriver.configure(params); - } catch (final ClassNotFoundException e) { - throw new ConfigurationException("Unable to find class for libvirt.vif.driver " + e); - } catch (final InstantiationException e) { - throw new ConfigurationException("Unable to instantiate class for libvirt.vif.driver " + e); - } catch (final IllegalAccessException e) { + } catch (final ReflectiveOperationException e) { throw new ConfigurationException("Unable to instantiate class for libvirt.vif.driver " + e); } return vifDriver; @@ -5290,6 +5319,24 @@ public void removeCheckpointsOnVm(String vmName, String volumeUuid, List logger.debug("Removed all checkpoints of volume [{}] on VM [{}].", volumeUuid, vmName); } + public Map getDiskPathLabelMap(String vmName) { + try { + Connect conn = LibvirtConnection.getConnectionByVmName(vmName); + List disks = getDisks(conn, vmName); + Map diskPathLabelMap = new HashMap<>(); + for (DiskDef disk : disks) { + if (disk.getDeviceType() != DeviceType.DISK) { + continue; + } + diskPathLabelMap.put(disk.getDiskPath(), disk.getDiskLabel()); + } + return diskPathLabelMap; + } catch (LibvirtException e) { + logger.error("Failed to get disk path label map for VM [{}] due to: [{}].", vmName, e.getMessage(), e); + throw new CloudRuntimeException(e); + } + } + public boolean recreateCheckpointsOnVm(List volumes, String vmName, Connect conn) { logger.debug("Trying to recreate checkpoints on VM [{}] with volumes [{}].", vmName, volumes); try { @@ -5778,16 +5825,6 @@ private HashMap> syncNetworkGroups(final long id) { return states; } - /* online snapshot supported by enhanced qemu-kvm */ - private boolean isSnapshotSupported() { - final String result = executeBashScript("qemu-img --help|grep convert"); - if (result != null) { - return false; - } else { - return true; - } - } - public Pair getNicStats(final String nicName) { return new Pair(readDouble(nicName, "rx_bytes"), readDouble(nicName, "tx_bytes")); } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java new file mode 100644 index 000000000000..a39e617c916a --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java @@ -0,0 +1,197 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.hypervisor.kvm.resource.wrapper; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.backup.CreateImageTransferAnswer; +import org.apache.cloudstack.backup.CreateImageTransferCommand; +import org.apache.cloudstack.storage.resource.IpTablesHelper; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.ImageServerControlSocket; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; + +@ResourceWrapper(handles = CreateImageTransferCommand.class) +public class LibvirtCreateImageTransferCommandWrapper extends CommandWrapper { + protected Logger logger = LogManager.getLogger(getClass()); + + private static final String IMAGE_SERVER_TLS_CERT_FILE = "/etc/cloudstack/agent/cloud.crt"; + private static final String IMAGE_SERVER_TLS_KEY_FILE = "/etc/cloudstack/agent/cloud.key"; + + @Override + public Answer execute(CreateImageTransferCommand cmd, LibvirtComputingResource resource) { + final String transferId = cmd.getTransferId(); + if (StringUtils.isBlank(transferId)) { + return new CreateImageTransferAnswer(cmd, false, "transferId is empty."); + } + if (StringUtils.isBlank(cmd.getToken())) { + return new CreateImageTransferAnswer(cmd, false, "transfer token is empty."); + } + + final Map payload = buildPayload(cmd); + if (payload == null) { + return new CreateImageTransferAnswer(cmd, false, "Invalid image transfer payload."); + } + + final int imageServerPort = LibvirtComputingResource.IMAGE_SERVER_DEFAULT_PORT; + final String listenAddress = getListenAddress(resource); + final String socketPath = resource.getImageServerSocketPath(); + if (StringUtils.isBlank(socketPath)) { + return new CreateImageTransferAnswer(cmd, false, "image server control socket path is empty."); + } + if (!startImageServerIfNeeded(socketPath, imageServerPort, listenAddress, resource)) { + return new CreateImageTransferAnswer(cmd, false, "Failed to start image server."); + } + if (!registerTransfer(socketPath, transferId, payload)) { + return new CreateImageTransferAnswer(cmd, false, "Failed to register transfer with image server."); + } + + final String transferScheme = resource.isImageServerTlsEnabled() ? "https" : "http"; + final String transferUrl = String.format("%s://%s:%d/images/%s", transferScheme, listenAddress, imageServerPort, transferId); + return new CreateImageTransferAnswer(cmd, true, "Image transfer prepared on KVM host.", transferId, transferUrl); + } + + protected Map buildPayload(CreateImageTransferCommand cmd) { + Map payload = new HashMap<>(); + payload.put("backend", cmd.getBackend().name()); + payload.put("idle_timeout_seconds", cmd.getIdleTimeoutSeconds()); + payload.put("token", cmd.getToken()); + + if (cmd.getBackend() == CreateImageTransferCommand.Backend.file) { + if (StringUtils.isBlank(cmd.getFile())) { + return null; + } + payload.put("file", cmd.getFile()); + return payload; + } + + if (StringUtils.isAnyBlank(cmd.getSocket(), cmd.getExportName())) { + return null; + } + String safeSocket = LibvirtStartNBDServerCommandWrapper.validateSafeName(cmd.getSocket(), "socket"); + if (safeSocket == null) { + return null; + } + payload.put("socket", LibvirtStartNBDServerCommandWrapper.socketPathFor(safeSocket)); + payload.put("export", cmd.getExportName()); + if (StringUtils.isNotBlank(cmd.getCheckpointId())) { + payload.put("export_bitmap", cmd.getCheckpointId()); + } + return payload; + } + + protected boolean startImageServerIfNeeded(String socketPath, int imageServerPort, String listenAddress, LibvirtComputingResource resource) { + String unitName = LibvirtComputingResource.IMAGE_SERVER_SYSTEMD_UNIT_NAME; + if (runCommand("systemctl", "is-active", "--quiet", unitName) == null && ImageServerControlSocket.isReady(socketPath)) { + openFirewallRule(imageServerPort); + return true; + } + + resetService(unitName); + String result = runCommand(buildImageServerStartCommand(imageServerPort, listenAddress, resource)); + if (result != null) { + logger.error("Failed to start image server: {}", result); + return false; + } + + int maxAttempts = 10; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + if (ImageServerControlSocket.isReady(socketPath)) { + openFirewallRule(imageServerPort); + return true; + } + sleep(1000); + } + return false; + } + + protected List buildImageServerStartCommand(int imageServerPort, String listenAddress, LibvirtComputingResource resource) { + String packageDir = resource.getImageServerPath(); + String parentDir = new File(packageDir).getParent(); + String moduleName = new File(packageDir).getName(); + List args = new ArrayList<>(); + args.add("systemd-run"); + args.add("--unit=" + LibvirtComputingResource.IMAGE_SERVER_SYSTEMD_UNIT_NAME); + args.add("--property=Restart=no"); + args.add("--property=WorkingDirectory=" + parentDir); + args.add("/usr/bin/python3"); + args.add("-m"); + args.add(moduleName); + args.add("--listen"); + args.add(listenAddress); + args.add("--port"); + args.add(String.valueOf(imageServerPort)); + if (resource.isImageServerTlsEnabled()) { + args.add("--tls-enabled"); + args.add("--tls-cert-file"); + args.add(IMAGE_SERVER_TLS_CERT_FILE); + args.add("--tls-key-file"); + args.add(IMAGE_SERVER_TLS_KEY_FILE); + } + return args; + } + + protected boolean registerTransfer(String socketPath, String transferId, Map payload) { + return ImageServerControlSocket.registerTransfer(socketPath, transferId, payload); + } + + protected void openFirewallRule(int imageServerPort) { + String rule = String.format("-p tcp -m state --state NEW -m tcp --dport %d -j ACCEPT", imageServerPort); + IpTablesHelper.addConditionally(IpTablesHelper.INPUT_CHAIN, true, rule, + String.format("Error in opening up image server port %d", imageServerPort)); + } + + protected String getListenAddress(LibvirtComputingResource resource) { + String listenAddress = resource.getImageServerListenAddress(); + return StringUtils.isBlank(listenAddress) ? resource.getPrivateIp() : listenAddress; + } + + protected void resetService(String unitName) { + runCommand("systemctl", "reset-failed", unitName); + } + + protected String runCommand(String... args) { + return runCommand(List.of(args)); + } + + protected String runCommand(List args) { + Script script = new Script(args.get(0), logger); + for (int index = 1; index < args.size(); index++) { + script.add(args.get(index)); + } + return script.execute(); + } + + protected void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVmCheckpointCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVmCheckpointCommandWrapper.java new file mode 100644 index 000000000000..fe9d496887e0 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVmCheckpointCommandWrapper.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.backup.DeleteVmCheckpointCommand; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; + +import java.util.Arrays; +import java.util.Map; + +@ResourceWrapper(handles = DeleteVmCheckpointCommand.class) +public class LibvirtDeleteVmCheckpointCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(DeleteVmCheckpointCommand command, LibvirtComputingResource resource) { + try { + if (command.isStoppedVM()) { + removeBitmapsForStoppedVm(command, resource); + } else { + deleteRunningVmCheckpointMetadata(command, resource); + } + return new Answer(command, true, null); + } catch (Exception e) { + logger.error("Failed to delete checkpoint [{}] on VM [{}].", command.getCheckpointId(), command.getVmName(), e); + return new Answer(command, false, e.getMessage()); + } finally { + clearPassphrases(command.getDiskPathPassphraseMap()); + } + } + + protected void deleteRunningVmCheckpointMetadata(DeleteVmCheckpointCommand command, LibvirtComputingResource resource) { + Script script = new Script("virsh", resource.getCmdsTimeout(), logger); + script.add("checkpoint-delete"); + script.add("--domain"); + script.add(command.getVmName()); + script.add("--checkpointname"); + script.add(command.getCheckpointId()); + script.add("--metadata"); + String result = script.execute(); + if (result != null) { + throw new RuntimeException(result); + } + } + + protected void removeBitmapsForStoppedVm(DeleteVmCheckpointCommand command, LibvirtComputingResource resource) throws Exception { + LibvirtStartBackupCommandWrapper bitmapWrapper = new LibvirtStartBackupCommandWrapper(); + QemuImg qemuImg = new QemuImg(resource.getCmdsTimeout()); + for (String diskPath : command.getDiskPathUuidMap().keySet()) { + try { + bitmapWrapper.runBitmapOperation(qemuImg, QemuImg.BitmapOperation.Remove, diskPath, command.getCheckpointId(), + getPassphrase(command.getDiskPathPassphraseMap(), diskPath)); + } catch (QemuImgException e) { + if (!isMissingBitmap(e)) { + throw e; + } + logger.warn("Could not delete dirty bitmap [{}] from disk [{}] because it was not found.", command.getCheckpointId(), diskPath); + } + } + } + + private boolean isMissingBitmap(QemuImgException e) { + return e.getMessage() != null && (e.getMessage().contains("Dirty bitmap") || e.getMessage().contains("not found")); + } + + private byte[] getPassphrase(Map diskPathPassphraseMap, String diskPath) { + if (diskPathPassphraseMap == null) { + return null; + } + return diskPathPassphraseMap.get(diskPath); + } + + private void clearPassphrases(Map diskPathPassphraseMap) { + if (diskPathPassphraseMap == null) { + return; + } + for (byte[] passphrase : diskPathPassphraseMap.values()) { + if (passphrase != null) { + Arrays.fill(passphrase, (byte) 0); + } + } + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFinalizeImageTransferCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFinalizeImageTransferCommandWrapper.java new file mode 100644 index 000000000000..109c28d8d0b1 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtFinalizeImageTransferCommandWrapper.java @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.hypervisor.kvm.resource.wrapper; + +import org.apache.cloudstack.backup.FinalizeImageTransferCommand; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.ImageServerControlSocket; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; + +@ResourceWrapper(handles = FinalizeImageTransferCommand.class) +public class LibvirtFinalizeImageTransferCommandWrapper extends CommandWrapper { + protected Logger logger = LogManager.getLogger(getClass()); + + @Override + public Answer execute(FinalizeImageTransferCommand cmd, LibvirtComputingResource resource) { + final String transferId = cmd.getTransferId(); + if (StringUtils.isBlank(transferId)) { + return new Answer(cmd, false, "transferId is empty."); + } + final String socketPath = resource.getImageServerSocketPath(); + if (StringUtils.isBlank(socketPath)) { + return new Answer(cmd, false, "image server control socket path is empty."); + } + + int activeTransfers = unregisterTransfer(socketPath, transferId); + if (activeTransfers < 0) { + stopImageServer(LibvirtComputingResource.IMAGE_SERVER_DEFAULT_PORT, resource); + return new Answer(cmd, true, "Image transfer finalized (server unreachable, forced stop)."); + } + if (activeTransfers == 0) { + stopImageServer(LibvirtComputingResource.IMAGE_SERVER_DEFAULT_PORT, resource); + } + return new Answer(cmd, true, "Image transfer finalized."); + } + + protected int unregisterTransfer(String socketPath, String transferId) { + return ImageServerControlSocket.unregisterTransfer(socketPath, transferId); + } + + protected boolean stopImageServer(int imageServerPort, LibvirtComputingResource resource) { + String unitName = LibvirtComputingResource.IMAGE_SERVER_SYSTEMD_UNIT_NAME; + runCommand("systemctl", "stop", unitName); + runCommand("systemctl", "reset-failed", unitName); + removeFirewallRule(imageServerPort); + return true; + } + + protected void removeFirewallRule(int port) { + runCommand("iptables", "-D", "INPUT", "-p", "tcp", "-m", "state", "--state", "NEW", "-m", "tcp", + "--dport", String.valueOf(port), "-j", "ACCEPT"); + } + + protected String runCommand(String... args) { + Script script = new Script(args[0], logger); + for (int index = 1; index < args.length; index++) { + script.add(args[index]); + } + return script.execute(); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java new file mode 100644 index 000000000000..995f7db636da --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartBackupCommandWrapper.java @@ -0,0 +1,192 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.backup.StartBackupAnswer; +import org.apache.cloudstack.backup.StartBackupCommand; +import org.apache.cloudstack.utils.cryptsetup.KeyFile; +import org.apache.cloudstack.utils.qemu.QemuImageOptions; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.apache.cloudstack.utils.qemu.QemuObject; +import org.apache.cloudstack.utils.security.ParserUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.libvirt.LibvirtException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.IOException; +import java.io.StringWriter; +import java.time.Instant; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +@ResourceWrapper(handles = StartBackupCommand.class) +public class LibvirtStartBackupCommandWrapper extends CommandWrapper { + + private static final String VIRSH_COMMAND = "virsh"; + private static final String QEMU_IMG_SECRET_NAME = "sec0"; + + @Override + public Answer execute(StartBackupCommand command, LibvirtComputingResource resource) { + try { + validate(command.getToCheckpointId(), command.getDiskPathUuidMap()); + if (command.isStoppedVM()) { + addBitmapsForStoppedVm(command, resource); + } else { + createCheckpointForRunningVm(command, resource); + } + return new StartBackupAnswer(command, true, null, Instant.now().getEpochSecond()); + } catch (Exception e) { + logger.error("Failed to start backup checkpoint [{}] on VM [{}].", command.getToCheckpointId(), command.getVmName(), e); + return new StartBackupAnswer(command, false, e.getMessage()); + } finally { + clearPassphrases(command.getDiskPathPassphraseMap()); + } + } + + protected void createCheckpointForRunningVm(StartBackupCommand command, LibvirtComputingResource resource) throws Exception { + Map diskPathLabelMap = resource.getDiskPathLabelMap(command.getVmName()); + String checkpointXml = buildCheckpointXml(command, diskPathLabelMap); + Path checkpointXmlPath = Files.createTempFile("cloudstack-checkpoint-", ".xml"); + try { + Files.write(checkpointXmlPath, checkpointXml.getBytes(StandardCharsets.UTF_8)); + Script script = new Script(VIRSH_COMMAND, resource.getCmdsTimeout(), logger); + script.add("checkpoint-create"); + script.add("--domain"); + script.add(command.getVmName()); + script.add("--xmlfile"); + script.add(checkpointXmlPath.toString()); + String result = script.execute(); + if (result != null) { + throw new CloudRuntimeException(result); + } + } finally { + Files.deleteIfExists(checkpointXmlPath); + } + } + + protected String buildCheckpointXml(StartBackupCommand command, Map diskPathLabelMap) throws Exception { + DocumentBuilder docBuilder = ParserUtils.getSaferDocumentBuilderFactory().newDocumentBuilder(); + Document document = docBuilder.newDocument(); + Element root = document.createElement("domaincheckpoint"); + document.appendChild(root); + + appendTextElement(document, root, "name", command.getToCheckpointId()); + if (StringUtils.isNotBlank(command.getFromCheckpointId())) { + Element parent = document.createElement("parent"); + appendTextElement(document, parent, "name", command.getFromCheckpointId()); + root.appendChild(parent); + } + + Element disks = document.createElement("disks"); + root.appendChild(disks); + for (String diskPath : command.getDiskPathUuidMap().keySet()) { + String diskLabel = diskPathLabelMap.get(diskPath); + if (StringUtils.isBlank(diskLabel)) { + throw new CloudRuntimeException(String.format("Unable to map disk path [%s] to a VM disk label.", diskPath)); + } + Element disk = document.createElement("disk"); + disk.setAttribute("name", diskLabel); + disk.setAttribute("checkpoint", "bitmap"); + disks.appendChild(disk); + } + + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(document), new StreamResult(writer)); + return writer.toString(); + } + + protected void addBitmapsForStoppedVm(StartBackupCommand command, LibvirtComputingResource resource) throws IOException, QemuImgException, LibvirtException { + QemuImg qemuImg = new QemuImg(resource.getCmdsTimeout()); + for (String diskPath : command.getDiskPathUuidMap().keySet()) { + runBitmapOperation(qemuImg, QemuImg.BitmapOperation.Add, diskPath, command.getToCheckpointId(), getPassphrase(command.getDiskPathPassphraseMap(), diskPath)); + } + } + + protected void runBitmapOperation(QemuImg qemuImg, QemuImg.BitmapOperation operation, String diskPath, String bitmapName, byte[] passphrase) + throws IOException, QemuImgException { + if (ArrayUtils.isEmpty(passphrase)) { + QemuImgFile volume = new QemuImgFile(diskPath, QemuImg.PhysicalDiskFormat.QCOW2); + qemuImg.bitmap(operation, volume, bitmapName); + return; + } + + try (KeyFile keyFile = new KeyFile(passphrase)) { + QemuImageOptions imageOptions = new QemuImageOptions(QemuImg.PhysicalDiskFormat.QCOW2, diskPath, QEMU_IMG_SECRET_NAME); + QemuObject secret = QemuObject.prepareSecretForQemuImg(QemuImg.PhysicalDiskFormat.QCOW2, QemuObject.EncryptFormat.LUKS, + keyFile.toString(), QEMU_IMG_SECRET_NAME, null); + qemuImg.bitmap(operation, imageOptions, Collections.singletonList(secret), bitmapName); + } + } + + private void appendTextElement(Document document, Element parent, String name, String value) { + Element element = document.createElement(name); + element.setTextContent(value); + parent.appendChild(element); + } + + private void validate(String checkpointId, Map diskPathUuidMap) { + if (StringUtils.isBlank(checkpointId)) { + throw new CloudRuntimeException("Checkpoint ID is required."); + } + if (MapUtils.isEmpty(diskPathUuidMap)) { + throw new CloudRuntimeException("At least one disk path is required."); + } + } + + private byte[] getPassphrase(Map diskPathPassphraseMap, String diskPath) { + if (MapUtils.isEmpty(diskPathPassphraseMap)) { + return null; + } + return diskPathPassphraseMap.get(diskPath); + } + + private void clearPassphrases(Map diskPathPassphraseMap) { + if (MapUtils.isEmpty(diskPathPassphraseMap)) { + return; + } + for (byte[] passphrase : diskPathPassphraseMap.values()) { + if (passphrase != null) { + Arrays.fill(passphrase, (byte) 0); + } + } + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartNBDServerCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartNBDServerCommandWrapper.java new file mode 100644 index 000000000000..224b3cdf14af --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStartNBDServerCommandWrapper.java @@ -0,0 +1,308 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.hypervisor.kvm.resource.wrapper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.apache.cloudstack.backup.StartNBDServerAnswer; +import org.apache.cloudstack.backup.StartNBDServerCommand; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONObject; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; + +@ResourceWrapper(handles = StartNBDServerCommand.class) +public class LibvirtStartNBDServerCommandWrapper extends CommandWrapper { + protected Logger logger = LogManager.getLogger(getClass()); + + private static final String SOCKET_DIR = "/tmp/imagetransfer"; + private static final Pattern SAFE_NAME = Pattern.compile("[A-Za-z0-9._-]{1,128}"); + private static final Set KEY_FILE_PERMISSIONS = PosixFilePermissions.fromString("rw-------"); + + @Override + public Answer execute(StartNBDServerCommand cmd, LibvirtComputingResource resource) { + String validationError = validate(cmd); + if (validationError != null) { + return new StartNBDServerAnswer(cmd, false, validationError); + } + + String safeTransferId = validateSafeName(cmd.getTransferId(), "transferId"); + String safeSocket = validateSafeName(cmd.getSocket(), "socket"); + String unitName = unitNameFor(safeTransferId); + String socketPath = socketPathFor(safeSocket); + Path keyFilePath = keyFilePathFor(safeTransferId); + + if (isNbdServiceActive(unitName)) { + return new StartNBDServerAnswer(cmd, false, "A qemu-nbd service is already running for the transfer."); + } + if (!ensureSocketDirectory()) { + return new StartNBDServerAnswer(cmd, false, "Failed to create qemu-nbd socket directory."); + } + + List command; + try { + command = buildQemuNbdStartCommand(cmd, unitName, socketPath, keyFilePath); + } catch (IOException e) { + deleteManagedKeyFile(keyFilePath); + logger.error("Failed to prepare qemu-nbd command", e); + return new StartNBDServerAnswer(cmd, false, "Failed to prepare qemu-nbd command: " + e.getMessage()); + } + + String result = runCommand(command); + if (result != null) { + deleteManagedKeyFile(keyFilePath); + logger.error("Failed to start qemu-nbd service [{}]: {}", unitName, result); + return new StartNBDServerAnswer(cmd, false, "Failed to start qemu-nbd service: " + result); + } + if (!waitForNbdService(unitName)) { + stopAndResetNbdService(unitName); + deleteManagedKeyFile(keyFilePath); + return new StartNBDServerAnswer(cmd, false, "qemu-nbd service failed to start."); + } + + return new StartNBDServerAnswer(cmd, true, "qemu-nbd service started.", cmd.getTransferId(), + String.format("nbd+unix:///%s?socket=%s", cmd.getExportName(), socketPath)); + } + + protected String validate(StartNBDServerCommand cmd) { + if (cmd == null) { + return "command is required."; + } + if (StringUtils.isBlank(cmd.getVolumePath())) { + return "Volume path is required for the nbd server."; + } + if (StringUtils.isBlank(cmd.getExportName())) { + return "Export name is required for the nbd server."; + } + if (StringUtils.isBlank(cmd.getSocket())) { + return "Socket is required for the nbd server."; + } + String transferError = unsafeNameError(cmd.getTransferId(), "transferId"); + if (transferError != null) { + return transferError; + } + return unsafeNameError(cmd.getSocket(), "socket"); + } + + static String validateSafeName(String value, String fieldName) { + return unsafeNameError(value, fieldName) == null ? value : null; + } + + static String unsafeNameError(String value, String fieldName) { + if (StringUtils.isBlank(value) || !SAFE_NAME.matcher(value).matches()) { + return fieldName + " contains unsafe characters."; + } + return null; + } + + static String unitNameFor(String safeTransferId) { + return "qemu-nbd-imagetransfer-" + safeTransferId; + } + + static String socketPathFor(String safeSocket) { + return SOCKET_DIR + "/" + safeSocket + ".sock"; + } + + static Path keyFilePathFor(String safeTransferId) { + return Path.of(SOCKET_DIR, safeTransferId + ".key"); + } + + protected List buildQemuNbdStartCommand(StartNBDServerCommand cmd, String unitName, String socketPath) throws IOException { + String safeTransferId = validateSafeName(cmd.getTransferId(), "transferId"); + return buildQemuNbdStartCommand(cmd, unitName, socketPath, safeTransferId == null ? null : keyFilePathFor(safeTransferId)); + } + + protected List buildQemuNbdStartCommand(StartNBDServerCommand cmd, String unitName, String socketPath, Path keyFilePath) throws IOException { + List args = new ArrayList<>(); + args.add("systemd-run"); + args.add("--unit=" + unitName); + args.add("--property=Restart=no"); + args.add("qemu-nbd"); + + byte[] passphrase = cmd.getPassphrase(); + String imageArg = cmd.getVolumePath(); + try { + if (passphrase != null && passphrase.length > 0) { + if (keyFilePath == null) { + throw new IOException("Safe transfer id is required for encrypted qemu-nbd exports."); + } + createManagedKeyFile(passphrase, keyFilePath); + args.add("--object"); + args.add(String.format("secret,id=sec0,file=%s", keyFilePath)); + args.add("--image-opts"); + imageArg = String.format("driver=qcow2,file.driver=file,file.filename=%s,encrypt.key-secret=sec0", cmd.getVolumePath()); + } + + args.add("--export-name"); + args.add(cmd.getExportName()); + args.add("--socket"); + args.add(socketPath); + args.add("--persistent"); + args.add("--shared=0"); + if (StringUtils.isNotBlank(cmd.getFromCheckpointId()) && isBitmapPresentOnDisk(cmd.getVolumePath(), cmd.getFromCheckpointId())) { + args.add("-B"); + args.add(cmd.getFromCheckpointId()); + } + if ("download".equalsIgnoreCase(cmd.getDirection())) { + args.add("--read-only"); + } + args.add(imageArg); + return args; + } catch (IOException | RuntimeException e) { + if (passphrase != null && passphrase.length > 0) { + deleteManagedKeyFile(keyFilePath); + } + throw e; + } finally { + cmd.clearPassphrase(); + } + } + + protected boolean isNbdServiceActive(String unitName) { + return runCommand("systemctl", "is-active", "--quiet", unitName) == null; + } + + protected boolean waitForNbdService(String unitName) { + int maxAttempts = 4; + for (int attempt = 0; attempt < maxAttempts; attempt++) { + sleep(5000); + if (isNbdServiceActive(unitName)) { + return true; + } + } + return false; + } + + protected boolean ensureSocketDirectory() { + File dir = new File(SOCKET_DIR); + return dir.exists() || dir.mkdirs(); + } + + protected Path createManagedKeyFile(byte[] passphrase, Path keyFilePath) throws IOException { + deleteManagedKeyFile(keyFilePath); + try { + Files.createFile(keyFilePath, PosixFilePermissions.asFileAttribute(KEY_FILE_PERMISSIONS)); + } catch (UnsupportedOperationException e) { + Files.createFile(keyFilePath); + setOwnerOnlyPermissions(keyFilePath); + } + Files.write(keyFilePath, passphrase, StandardOpenOption.WRITE); + setOwnerOnlyPermissions(keyFilePath); + return keyFilePath; + } + + protected void setOwnerOnlyPermissions(Path keyFilePath) throws IOException { + try { + Files.setPosixFilePermissions(keyFilePath, KEY_FILE_PERMISSIONS); + } catch (UnsupportedOperationException e) { + File keyFile = keyFilePath.toFile(); + boolean permissionsUpdated = keyFile.setReadable(false, false) + && keyFile.setReadable(true, true) + && keyFile.setWritable(false, false) + && keyFile.setWritable(true, true) + && keyFile.setExecutable(false, false); + if (!permissionsUpdated) { + throw new IOException("Failed to set owner-only permissions on qemu-nbd key file."); + } + } + } + + protected void deleteManagedKeyFile(Path keyFilePath) { + if (keyFilePath == null) { + return; + } + try { + Files.deleteIfExists(keyFilePath); + } catch (IOException e) { + logger.warn("Failed to delete qemu-nbd key file [{}].", keyFilePath, e); + } + } + + protected void stopAndResetNbdService(String unitName) { + runCommand("systemctl", "stop", unitName); + runCommand("systemctl", "reset-failed", unitName); + } + + protected String runCommand(String... args) { + return runCommand(List.of(args)); + } + + protected String runCommand(List args) { + Script script = new Script(args.get(0), logger); + for (int index = 1; index < args.size(); index++) { + script.add(args.get(index)); + } + return script.execute(); + } + + protected void sleep(long millis) { + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + protected boolean isBitmapPresentOnDisk(String volumePath, String fromCheckpointId) { + String qemuImgInfo = Script.runBashScriptIgnoreExitValue(String.format("qemu-img info --output=json %s", volumePath), 0); + if (StringUtils.isBlank(qemuImgInfo)) { + logger.warn("Unable to read qemu-img info output for disk path [{}].", volumePath); + return false; + } + try { + JSONObject info = new JSONObject(qemuImgInfo); + JSONObject formatSpecific = info.optJSONObject("format-specific"); + if (formatSpecific == null) { + return false; + } + JSONObject formatData = formatSpecific.optJSONObject("data"); + if (formatData == null) { + return false; + } + JSONArray bitmaps = formatData.optJSONArray("bitmaps"); + if (bitmaps == null) { + return false; + } + for (int index = 0; index < bitmaps.length(); index++) { + JSONObject bitmap = bitmaps.optJSONObject(index); + if (bitmap != null && fromCheckpointId.equals(bitmap.optString("name"))) { + return true; + } + } + } catch (Exception e) { + logger.warn("Failed to parse qemu-img info output for disk path [{}].", volumePath, e); + } + return false; + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopBackupCommandWrapper.java new file mode 100644 index 000000000000..8f91209a5226 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopBackupCommandWrapper.java @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.backup.StopBackupAnswer; +import org.apache.cloudstack.backup.StopBackupCommand; + +@ResourceWrapper(handles = StopBackupCommand.class) +public class LibvirtStopBackupCommandWrapper extends CommandWrapper { + + @Override + public Answer execute(StopBackupCommand command, LibvirtComputingResource resource) { + Script script = new Script("virsh", resource.getCmdsTimeout(), logger); + script.add("backup-end"); + script.add("--domain"); + script.add(command.getVmName()); + String result = script.execute(); + if (result != null) { + return new StopBackupAnswer(command, false, result); + } + return new StopBackupAnswer(command, true, null); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopNBDServerCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopNBDServerCommandWrapper.java new file mode 100644 index 000000000000..c2b28835fb1b --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopNBDServerCommandWrapper.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.hypervisor.kvm.resource.wrapper; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.cloudstack.backup.StopNBDServerCommand; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; + +@ResourceWrapper(handles = StopNBDServerCommand.class) +public class LibvirtStopNBDServerCommandWrapper extends CommandWrapper { + protected Logger logger = LogManager.getLogger(getClass()); + + @Override + public Answer execute(StopNBDServerCommand cmd, LibvirtComputingResource resource) { + if (cmd == null || StringUtils.isBlank(cmd.getTransferId())) { + return new Answer(cmd, false, "transferId is empty."); + } + String error = LibvirtStartNBDServerCommandWrapper.unsafeNameError(cmd.getTransferId(), "transferId"); + if (error != null) { + return new Answer(cmd, false, error); + } + + String unitName = LibvirtStartNBDServerCommandWrapper.unitNameFor(cmd.getTransferId()); + if (!stopNbdService(unitName)) { + return new Answer(cmd, false, "Failed to stop qemu-nbd service."); + } + deleteSocketFile(LibvirtStartNBDServerCommandWrapper.socketPathFor(cmd.getTransferId())); + deleteManagedKeyFile(LibvirtStartNBDServerCommandWrapper.keyFilePathFor(cmd.getTransferId())); + return new Answer(cmd, true, "Image transfer finalized."); + } + + protected boolean stopNbdService(String unitName) { + runCommand("systemctl", "stop", unitName); + runCommand("systemctl", "reset-failed", unitName); + return true; + } + + protected void deleteSocketFile(String socketPath) { + File socketFile = new File(socketPath); + if (socketFile.exists() && !socketFile.delete()) { + logger.warn("Failed to delete qemu-nbd socket file [{}].", socketPath); + } + } + + protected void deleteManagedKeyFile(Path keyFilePath) { + try { + Files.deleteIfExists(keyFilePath); + } catch (IOException e) { + logger.warn("Failed to delete qemu-nbd key file [{}].", keyFilePath, e); + } + } + + protected String runCommand(String... args) { + Script script = new Script(args[0], logger); + for (int index = 1; index < args.length; index++) { + script.add(args[index]); + } + return script.execute(); + } +} diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java index f0b5cfc337de..0d1ef03d82fc 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAProvider.java @@ -36,7 +36,7 @@ import org.apache.cloudstack.outofbandmanagement.OutOfBandManagementService; import org.joda.time.DateTime; -import javax.inject.Inject; +import jakarta.inject.Inject; public final class KVMHAProvider extends HAAbstractHostProvider implements HAProvider, Configurable { diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java index af7441c4fd29..882440d8d2ee 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java @@ -45,7 +45,7 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.commons.lang.ArrayUtils; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.util.ArrayList; import org.joda.time.DateTime; import java.util.HashMap; diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java index 1fec561dc890..f75e2f0c73b9 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImg.java @@ -163,6 +163,9 @@ public QemuImg(final int timeout) throws LibvirtException, QemuImgException { this(timeout, false, false); } + protected QemuImg() { + } + /** * Sets the timeout of the scripts executed by this QemuImg object. * @@ -978,7 +981,7 @@ public String checkAndRepair(final QemuImgFile file, final QemuImageOptions imag *
* This method is a facade for 'qemu-img bitmap'. *
- * Currently only the {@link BitmapOperation#Remove} is implemented + * Currently only {@link BitmapOperation#Add} and {@link BitmapOperation#Remove} are implemented. * * @param bitmapOperation * The operation to be performed @@ -988,22 +991,66 @@ public String checkAndRepair(final QemuImgFile file, final QemuImageOptions imag * The name of the bitmap */ public void bitmap(BitmapOperation bitmapOperation, QemuImgFile srcfile, String bitmapName) throws QemuImgException { - if (bitmapOperation != BitmapOperation.Remove) { - throw new QemuImgException("Operation not implemented."); + final Script script = newScript(); + script.add("bitmap"); + script.add(getBitmapOperationFlag(bitmapOperation)); + script.add(srcfile.getFileName()); + script.add(bitmapName); + + String result = script.execute(); + if (result != null) { + throw new QemuImgException(String.format("Exception while running qemu-img bitmap operation [%s] on bitmap [%s]. Result is [%s].", bitmapOperation, bitmapName, result)); } - removeBitmap(srcfile, bitmapName); } - private void removeBitmap(QemuImgFile srcFile, String bitmapName) throws QemuImgException { - final Script script = new Script(_qemuImgPath); + /** + * Perform one or more modifications of the persistent bitmap in {@code imageOptions}, including encrypted images. + * + * @param bitmapOperation + * The operation to be performed + * @param imageOptions + * Qemu style image options to identify the image, optionally including encrypted qcow2 key secret details + * @param qemuObjects + * Qemu style objects, such as secret objects for encrypted images + * @param bitmapName + * The name of the bitmap + */ + public void bitmap(BitmapOperation bitmapOperation, QemuImageOptions imageOptions, List qemuObjects, String bitmapName) throws QemuImgException { + executeBitmap(bitmapOperation, qemuObjects, imageOptions.toCommandFlag(), bitmapName); + } + + private void executeBitmap(BitmapOperation bitmapOperation, List qemuObjects, String[] imageCommandFlag, String bitmapName) throws QemuImgException { + final Script script = newScript(); script.add("bitmap"); - script.add("--remove"); - script.add(srcFile.getFileName()); + script.add(getBitmapOperationFlag(bitmapOperation)); + + if (qemuObjects != null) { + for (QemuObject object : qemuObjects) { + script.add(object.toCommandFlag()); + } + } + + script.add(imageCommandFlag); script.add(bitmapName); String result = script.execute(); if (result != null) { - throw new QemuImgException(String.format("Exception while removing bitmap [%s] from file [%s]. Result is [%s].", srcFile.getFileName(), bitmapName, result)); + throw new QemuImgException(String.format("Exception while running qemu-img bitmap operation [%s] on bitmap [%s]. Result is [%s].", bitmapOperation, bitmapName, result)); + } + } + + protected Script newScript() { + return new Script(_qemuImgPath); + } + + private String getBitmapOperationFlag(BitmapOperation bitmapOperation) throws QemuImgException { + switch (bitmapOperation) { + case Add: + return "--add"; + case Remove: + return "--remove"; + default: + throw new QemuImgException("Operation not implemented."); } } } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapperTest.java new file mode 100644 index 000000000000..2a5de558bbeb --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapperTest.java @@ -0,0 +1,101 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.hypervisor.kvm.resource.wrapper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.Map; + +import org.apache.cloudstack.backup.CreateImageTransferAnswer; +import org.apache.cloudstack.backup.CreateImageTransferCommand; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtCreateImageTransferCommandWrapperTest { + @Mock + private LibvirtComputingResource resource; + + private RecordingCreateWrapper wrapper; + + @Before + public void setUp() { + wrapper = new RecordingCreateWrapper(); + when(resource.getImageServerSocketPath()).thenReturn("/run/cloudstack/image-server.sock"); + when(resource.getImageServerListenAddress()).thenReturn("10.1.1.10"); + when(resource.isImageServerTlsEnabled()).thenReturn(false); + } + + @Test + public void testExecuteRejectsMissingTransferToken() { + CreateImageTransferCommand command = new CreateImageTransferCommand("transfer-1", "upload", + "socket-1", "/var/lib/images/disk.qcow2", 60, ""); + + Answer answer = wrapper.execute(command, resource); + + assertFalse(answer.getResult()); + assertTrue(answer.getDetails().contains("token")); + } + + @Test + public void testExecuteRegistersFileTransferWithControlSocketPathAndToken() { + CreateImageTransferCommand command = new CreateImageTransferCommand("transfer-1", "upload", + "socket-1", "/var/lib/images/disk.qcow2", 60, "secret-token"); + + CreateImageTransferAnswer answer = (CreateImageTransferAnswer) wrapper.execute(command, resource); + + assertTrue(answer.getResult()); + assertEquals("/run/cloudstack/image-server.sock", wrapper.socketPath); + assertEquals("transfer-1", wrapper.transferId); + assertEquals("file", wrapper.payload.get("backend")); + assertEquals("/var/lib/images/disk.qcow2", wrapper.payload.get("file")); + assertEquals(60, wrapper.payload.get("idle_timeout_seconds")); + assertEquals("secret-token", wrapper.payload.get("token")); + assertEquals("http://10.1.1.10:54322/images/transfer-1", answer.getTransferUrl()); + } + + private static class RecordingCreateWrapper extends LibvirtCreateImageTransferCommandWrapper { + private String socketPath; + private String transferId; + private Map payload; + + @Override + protected boolean startImageServerIfNeeded(String socketPath, int imageServerPort, String listenAddress, + LibvirtComputingResource resource) { + return true; + } + + @Override + protected boolean registerTransfer(String socketPath, String transferId, Map payload) { + this.socketPath = socketPath; + this.transferId = transferId; + this.payload = payload; + return true; + } + } +} diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVmCheckpointCommandWrapperTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVmCheckpointCommandWrapperTest.java new file mode 100644 index 000000000000..a27b62a3b363 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVmCheckpointCommandWrapperTest.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.backup.DeleteVmCheckpointCommand; +import org.apache.cloudstack.utils.qemu.QemuImageOptions; +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.HashMap; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class LibvirtDeleteVmCheckpointCommandWrapperTest { + + private LibvirtDeleteVmCheckpointCommandWrapper wrapper; + private LibvirtComputingResource resource; + + @Before + public void setUp() { + wrapper = new LibvirtDeleteVmCheckpointCommandWrapper(); + resource = Mockito.mock(LibvirtComputingResource.class); + when(resource.getCmdsTimeout()).thenReturn(30); + } + + @Test + public void executeDeletesRunningVmCheckpointMetadata() { + DeleteVmCheckpointCommand command = new DeleteVmCheckpointCommand("i-2-VM", "checkpoint-2", null, false); + + try (MockedConstruction -

-
-
-
-
- - - it('should auto compile', function() { - expect(element('div[compile]').text()).toBe('Hello Angular'); - input('html').enter('{{name}}!'); - expect(element('div[compile]').text()).toBe('Angular!'); - }); - - - - * - * - * @param {string|DOMElement} element Element or HTML string to compile into a template function. - * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. - * @param {number} maxPriority only apply directives lower then given priority (Only effects the - * root element(s), not their children) - * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template - * (a DOM element/tree) to a scope. Where: - * - * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. - * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the - * `template` and call the `cloneAttachFn` function allowing the caller to attach the - * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is - * called as:
`cloneAttachFn(clonedElement, scope)` where: - * - * * `clonedElement` - is a clone of the original `element` passed into the compiler. - * * `scope` - is the current scope with which the linking function is working with. - * - * Calling the linking function returns the element of the template. It is either the original element - * passed in, or the clone of the element if the `cloneAttachFn` is provided. - * - * After linking the view is not updated until after a call to $digest which typically is done by - * Angular automatically. - * - * If you need access to the bound view, there are two ways to do it: - * - * - If you are not asking the linking function to clone the template, create the DOM element(s) - * before you send them to the compiler and keep this reference around. - *
- *     var element = $compile('

{{total}}

')(scope); - *
- * - * - if on the other hand, you need the element to be cloned, the view reference from the original - * example would not point to the clone, but rather to the original template that was cloned. In - * this case, you can access the clone via the cloneAttachFn: - *
- *     var templateHTML = angular.element('

{{total}}

'), - * scope = ....; - * - * var clonedElement = $compile(templateHTML)(scope, function(clonedElement, scope) { - * //attach the clone to DOM document at the right place - * }); - * - * //now we have reference to the cloned DOM via `clone` - *
- * - * - * For information on how the compiler works, see the - * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. - */ - - -/** - * @ngdoc service - * @name ng.$compileProvider - * @function - * - * @description - */ -$CompileProvider.$inject = ['$provide']; -function $CompileProvider($provide) { - var hasDirectives = {}, - Suffix = 'Directive', - COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, - CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, - MULTI_ROOT_TEMPLATE_ERROR = 'Template must have exactly one root element. was: ', - urlSanitizationAllowlist = /^\s*(https?|ftp|mailto|file):/; - - - /** - * @ngdoc function - * @name ng.$compileProvider#directive - * @methodOf ng.$compileProvider - * @function - * - * @description - * Register a new directives with the compiler. - * - * @param {string} name Name of the directive in camel-case. (ie ngBind which will match as - * ng-bind). - * @param {function} directiveFactory An injectable directive factroy function. See {@link guide/directive} for more - * info. - * @returns {ng.$compileProvider} Self for chaining. - */ - this.directive = function registerDirective(name, directiveFactory) { - if (isString(name)) { - assertArg(directiveFactory, 'directive'); - if (!hasDirectives.hasOwnProperty(name)) { - hasDirectives[name] = []; - $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', - function($injector, $exceptionHandler) { - var directives = []; - forEach(hasDirectives[name], function(directiveFactory) { - try { - var directive = $injector.invoke(directiveFactory); - if (isFunction(directive)) { - directive = { compile: valueFn(directive) }; - } else if (!directive.compile && directive.link) { - directive.compile = valueFn(directive.link); - } - directive.priority = directive.priority || 0; - directive.name = directive.name || name; - directive.require = directive.require || (directive.controller && directive.name); - directive.restrict = directive.restrict || 'A'; - directives.push(directive); - } catch (e) { - $exceptionHandler(e); - } - }); - return directives; - }]); - } - hasDirectives[name].push(directiveFactory); - } else { - forEach(name, reverseParams(registerDirective)); - } - return this; - }; - - - /** - * @ngdoc function - * @name ng.$compileProvider#urlSanitizationAllowlist - * @methodOf ng.$compileProvider - * @function - * - * @description - * Retrieves or overrides the default regular expression that is used for allow listing of safe - * urls during a[href] sanitization. - * - * The sanitization is a security measure aimed at prevent XSS attacks via html links. - * - * Any url about to be assigned to a[href] via data-binding is first normalized and turned into an - * absolute url. Afterwards the url is matched against the `urlSanitizationAllowlist` regular - * expression. If a match is found the original url is written into the dom. Otherwise the - * absolute url is prefixed with `'unsafe:'` string and only then it is written into the DOM. - * - * @param {RegExp=} regexp New regexp to allow list urls with. - * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for - * chaining otherwise. - */ - this.urlSanitizationAllowlist = function(regexp) { - if (isDefined(regexp)) { - urlSanitizationAllowlist = regexp; - return this; - } - return urlSanitizationAllowlist; - }; - - - this.$get = [ - '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', - '$controller', '$rootScope', '$document', - function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, - $controller, $rootScope, $document) { - - var Attributes = function(element, attr) { - this.$$element = element; - this.$attr = attr || {}; - }; - - Attributes.prototype = { - $normalize: directiveNormalize, - - - /** - * Set a normalized attribute on the element in a way such that all directives - * can share the attribute. This function properly handles boolean attributes. - * @param {string} key Normalized key. (ie ngAttribute) - * @param {string|boolean} value The value to set. If `null` attribute will be deleted. - * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. - * Defaults to true. - * @param {string=} attrName Optional none normalized name. Defaults to key. - */ - $set: function(key, value, writeAttr, attrName) { - var booleanKey = getBooleanAttrName(this.$$element[0], key), - $$observers = this.$$observers, - normalizedVal; - - if (booleanKey) { - this.$$element.prop(key, value); - attrName = booleanKey; - } - - this[key] = value; - - // translate normalized key to actual key - if (attrName) { - this.$attr[key] = attrName; - } else { - attrName = this.$attr[key]; - if (!attrName) { - this.$attr[key] = attrName = snake_case(key, '-'); - } - } - - - // sanitize a[href] values - if (nodeName_(this.$$element[0]) === 'A' && key === 'href') { - urlSanitizationNode.setAttribute('href', value); - - // href property always returns normalized absolute url, so we can match against that - normalizedVal = urlSanitizationNode.href; - if (!normalizedVal.match(urlSanitizationAllowlist)) { - this[key] = value = 'unsafe:' + normalizedVal; - } - } - - - if (writeAttr !== false) { - if (value === null || value === undefined) { - this.$$element.removeAttr(attrName); - } else { - this.$$element.attr(attrName, value); - } - } - - // fire observers - $$observers && forEach($$observers[key], function(fn) { - try { - fn(value); - } catch (e) { - $exceptionHandler(e); - } - }); - }, - - - /** - * Observe an interpolated attribute. - * The observer will never be called, if given attribute is not interpolated. - * - * @param {string} key Normalized key. (ie ngAttribute) . - * @param {function(*)} fn Function that will be called whenever the attribute value changes. - * @returns {function(*)} the `fn` Function passed in. - */ - $observe: function(key, fn) { - var attrs = this, - $$observers = (attrs.$$observers || (attrs.$$observers = {})), - listeners = ($$observers[key] || ($$observers[key] = [])); - - listeners.push(fn); - $rootScope.$evalAsync(function() { - if (!listeners.$$inter) { - // no one registered attribute interpolation function, so lets call it manually - fn(attrs[key]); - } - }); - return fn; - } - }; - - var urlSanitizationNode = $document[0].createElement('a'), - startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(), - denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') - ? identity - : function denormalizeTemplate(template) { - return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); - }; - - - return compile; - - //================================ - - function compile($compileNodes, transcludeFn, maxPriority) { - if (!($compileNodes instanceof jqLite)) { - // jquery always rewraps, whereas we need to preserve the original selector so that we can modify it. - $compileNodes = jqLite($compileNodes); - } - // We can not compile top level text elements since text nodes can be merged and we will - // not be able to attach scope data to them, so we will wrap them in - forEach($compileNodes, function(node, index){ - if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { - $compileNodes[index] = jqLite(node).wrap('').parent()[0]; - } - }); - var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, maxPriority); - return function publicLinkFn(scope, cloneConnectFn){ - assertArg(scope, 'scope'); - // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart - // and sometimes changes the structure of the DOM. - var $linkNode = cloneConnectFn - ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! - : $compileNodes; - - // Attach scope only to non-text nodes. - for(var i = 0, ii = $linkNode.length; i - addDirective(directives, - directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority); - - // iterate over the attributes - for (var attr, name, nName, value, nAttrs = node.attributes, - j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { - attr = nAttrs[j]; - if (attr.specified) { - name = attr.name; - nName = directiveNormalize(name.toLowerCase()); - attrsMap[nName] = name; - attrs[nName] = value = trim((msie && name == 'href') - ? decodeURIComponent(node.getAttribute(name, 2)) - : attr.value); - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true - } - addAttrInterpolateDirective(node, directives, value, nName); - addDirective(directives, nName, 'A', maxPriority); - } - } - - // use class as directive - className = node.className; - if (isString(className) && className !== '') { - while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { - nName = directiveNormalize(match[2]); - if (addDirective(directives, nName, 'C', maxPriority)) { - attrs[nName] = trim(match[3]); - } - className = className.substr(match.index + match[0].length); - } - } - break; - case 3: /* Text Node */ - addTextInterpolateDirective(directives, node.nodeValue); - break; - case 8: /* Comment */ - try { - match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); - if (match) { - nName = directiveNormalize(match[1]); - if (addDirective(directives, nName, 'M', maxPriority)) { - attrs[nName] = trim(match[2]); - } - } - } catch (e) { - // turns out that under some circumstances IE9 throws errors when one attempts to read comment's node value. - // Just ignore it and continue. (Can't seem to reproduce in test case.) - } - break; - } - - directives.sort(byPriority); - return directives; - } - - - /** - * Once the directives have been collected, their compile functions are executed. This method - * is responsible for inlining directive templates as well as terminating the application - * of the directives if the terminal directive has been reached. - * - * @param {Array} directives Array of collected directives to execute their compile function. - * this needs to be pre-sorted by priority order. - * @param {Node} compileNode The raw DOM node to apply the compile functions to - * @param {Object} templateAttrs The shared attribute function - * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the - * scope argument is auto-generated to the new child of the transcluded parent scope. - * @param {JQLite} jqCollection If we are working on the root of the compile tree then this - * argument has the root jqLite array so that we can replace nodes on it. - * @returns linkFn - */ - function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection) { - var terminalPriority = -Number.MAX_VALUE, - preLinkFns = [], - postLinkFns = [], - newScopeDirective = null, - newIsolateScopeDirective = null, - templateDirective = null, - $compileNode = templateAttrs.$$element = jqLite(compileNode), - directive, - directiveName, - $template, - transcludeDirective, - childTranscludeFn = transcludeFn, - controllerDirectives, - linkFn, - directiveValue; - - // executes all directives on the current element - for(var i = 0, ii = directives.length; i < ii; i++) { - directive = directives[i]; - $template = undefined; - - if (terminalPriority > directive.priority) { - break; // prevent further processing of directives - } - - if (directiveValue = directive.scope) { - assertNoDuplicate('isolated scope', newIsolateScopeDirective, directive, $compileNode); - if (isObject(directiveValue)) { - safeAddClass($compileNode, 'ng-isolate-scope'); - newIsolateScopeDirective = directive; - } - safeAddClass($compileNode, 'ng-scope'); - newScopeDirective = newScopeDirective || directive; - } - - directiveName = directive.name; - - if (directiveValue = directive.controller) { - controllerDirectives = controllerDirectives || {}; - assertNoDuplicate("'" + directiveName + "' controller", - controllerDirectives[directiveName], directive, $compileNode); - controllerDirectives[directiveName] = directive; - } - - if (directiveValue = directive.transclude) { - assertNoDuplicate('transclusion', transcludeDirective, directive, $compileNode); - transcludeDirective = directive; - terminalPriority = directive.priority; - if (directiveValue == 'element') { - $template = jqLite(compileNode); - $compileNode = templateAttrs.$$element = - jqLite(document.createComment(' ' + directiveName + ': ' + templateAttrs[directiveName] + ' ')); - compileNode = $compileNode[0]; - replaceWith(jqCollection, jqLite($template[0]), compileNode); - childTranscludeFn = compile($template, transcludeFn, terminalPriority); - } else { - $template = jqLite(JQLiteClone(compileNode)).contents(); - $compileNode.html(''); // clear contents - childTranscludeFn = compile($template, transcludeFn); - } - } - - if ((directiveValue = directive.template)) { - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - directiveValue = denormalizeTemplate(directiveValue); - - if (directive.replace) { - $template = jqLite('
' + - trim(directiveValue) + - '
').contents(); - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== 1) { - throw new Error(MULTI_ROOT_TEMPLATE_ERROR + directiveValue); - } - - replaceWith(jqCollection, $compileNode, compileNode); - - var newTemplateAttrs = {$attr: {}}; - - // combine directives from the original node and from the template: - // - take the array of directives for this element - // - split it into two parts, those that were already applied and those that weren't - // - collect directives from the template, add them to the second group and sort them - // - append the second group with new directives to the first group - directives = directives.concat( - collectDirectives( - compileNode, - directives.splice(i + 1, directives.length - (i + 1)), - newTemplateAttrs - ) - ); - mergeTemplateAttributes(templateAttrs, newTemplateAttrs); - - ii = directives.length; - } else { - $compileNode.html(directiveValue); - } - } - - if (directive.templateUrl) { - assertNoDuplicate('template', templateDirective, directive, $compileNode); - templateDirective = directive; - nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), - nodeLinkFn, $compileNode, templateAttrs, jqCollection, directive.replace, - childTranscludeFn); - ii = directives.length; - } else if (directive.compile) { - try { - linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); - if (isFunction(linkFn)) { - addLinkFns(null, linkFn); - } else if (linkFn) { - addLinkFns(linkFn.pre, linkFn.post); - } - } catch (e) { - $exceptionHandler(e, startingTag($compileNode)); - } - } - - if (directive.terminal) { - nodeLinkFn.terminal = true; - terminalPriority = Math.max(terminalPriority, directive.priority); - } - - } - - nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope; - nodeLinkFn.transclude = transcludeDirective && childTranscludeFn; - - // might be normal or delayed nodeLinkFn depending on if templateUrl is present - return nodeLinkFn; - - //////////////////// - - function addLinkFns(pre, post) { - if (pre) { - pre.require = directive.require; - preLinkFns.push(pre); - } - if (post) { - post.require = directive.require; - postLinkFns.push(post); - } - } - - - function getControllers(require, $element) { - var value, retrievalMethod = 'data', optional = false; - if (isString(require)) { - while((value = require.charAt(0)) == '^' || value == '?') { - require = require.substr(1); - if (value == '^') { - retrievalMethod = 'inheritedData'; - } - optional = optional || value == '?'; - } - value = $element[retrievalMethod]('$' + require + 'Controller'); - if (!value && !optional) { - throw Error("No controller: " + require); - } - return value; - } else if (isArray(require)) { - value = []; - forEach(require, function(require) { - value.push(getControllers(require, $element)); - }); - } - return value; - } - - - function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { - var attrs, $element, i, ii, linkFn, controller; - - if (compileNode === linkNode) { - attrs = templateAttrs; - } else { - attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); - } - $element = attrs.$$element; - - if (newIsolateScopeDirective) { - var LOCAL_REGEXP = /^\s*([@=&])\s*(\w*)\s*$/; - - var parentScope = scope.$parent || scope; - - forEach(newIsolateScopeDirective.scope, function(definiton, scopeName) { - var match = definiton.match(LOCAL_REGEXP) || [], - attrName = match[2]|| scopeName, - mode = match[1], // @, =, or & - lastValue, - parentGet, parentSet; - - scope.$$isolateBindings[scopeName] = mode + attrName; - - switch (mode) { - - case '@': { - attrs.$observe(attrName, function(value) { - scope[scopeName] = value; - }); - attrs.$$observers[attrName].$$scope = parentScope; - break; - } - - case '=': { - parentGet = $parse(attrs[attrName]); - parentSet = parentGet.assign || function() { - // reset the change, or we will throw this exception on every $digest - lastValue = scope[scopeName] = parentGet(parentScope); - throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + attrs[attrName] + - ' (directive: ' + newIsolateScopeDirective.name + ')'); - }; - lastValue = scope[scopeName] = parentGet(parentScope); - scope.$watch(function parentValueWatch() { - var parentValue = parentGet(parentScope); - - if (parentValue !== scope[scopeName]) { - // we are out of sync and need to copy - if (parentValue !== lastValue) { - // parent changed and it has precedence - lastValue = scope[scopeName] = parentValue; - } else { - // if the parent can be assigned then do so - parentSet(parentScope, parentValue = lastValue = scope[scopeName]); - } - } - return parentValue; - }); - break; - } - - case '&': { - parentGet = $parse(attrs[attrName]); - scope[scopeName] = function(locals) { - return parentGet(parentScope, locals); - }; - break; - } - - default: { - throw Error('Invalid isolate scope definition for directive ' + - newIsolateScopeDirective.name + ': ' + definiton); - } - } - }); - } - - if (controllerDirectives) { - forEach(controllerDirectives, function(directive) { - var locals = { - $scope: scope, - $element: $element, - $attrs: attrs, - $transclude: boundTranscludeFn - }; - - controller = directive.controller; - if (controller == '@') { - controller = attrs[directive.name]; - } - - $element.data( - '$' + directive.name + 'Controller', - $controller(controller, locals)); - }); - } - - // PRELINKING - for(i = 0, ii = preLinkFns.length; i < ii; i++) { - try { - linkFn = preLinkFns[i]; - linkFn(scope, $element, attrs, - linkFn.require && getControllers(linkFn.require, $element)); - } catch (e) { - $exceptionHandler(e, startingTag($element)); - } - } - - // RECURSION - childLinkFn && childLinkFn(scope, linkNode.childNodes, undefined, boundTranscludeFn); - - // POSTLINKING - for(i = 0, ii = postLinkFns.length; i < ii; i++) { - try { - linkFn = postLinkFns[i]; - linkFn(scope, $element, attrs, - linkFn.require && getControllers(linkFn.require, $element)); - } catch (e) { - $exceptionHandler(e, startingTag($element)); - } - } - } - } - - - /** - * looks up the directive and decorates it with exception handling and proper parameters. We - * call this the boundDirective. - * - * @param {string} name name of the directive to look up. - * @param {string} location The directive must be found in specific format. - * String containing any of theses characters: - * - * * `E`: element name - * * `A': attribute - * * `C`: class - * * `M`: comment - * @returns true if directive was added. - */ - function addDirective(tDirectives, name, location, maxPriority) { - var match = false; - if (hasDirectives.hasOwnProperty(name)) { - for(var directive, directives = $injector.get(name + Suffix), - i = 0, ii = directives.length; i directive.priority) && - directive.restrict.indexOf(location) != -1) { - tDirectives.push(directive); - match = true; - } - } catch(e) { $exceptionHandler(e); } - } - } - return match; - } - - - /** - * When the element is replaced with HTML template then the new attributes - * on the template need to be merged with the existing attributes in the DOM. - * The desired effect is to have both of the attributes present. - * - * @param {object} dst destination attributes (original DOM) - * @param {object} src source attributes (from the directive template) - */ - function mergeTemplateAttributes(dst, src) { - var srcAttr = src.$attr, - dstAttr = dst.$attr, - $element = dst.$$element; - - // reapply the old attributes to the new element - forEach(dst, function(value, key) { - if (key.charAt(0) != '$') { - if (src[key]) { - value += (key === 'style' ? ';' : ' ') + src[key]; - } - dst.$set(key, value, true, srcAttr[key]); - } - }); - - // copy the new attributes on the old attrs object - forEach(src, function(value, key) { - if (key == 'class') { - safeAddClass($element, value); - dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; - } else if (key == 'style') { - $element.attr('style', $element.attr('style') + ';' + value); - } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { - dst[key] = value; - dstAttr[key] = srcAttr[key]; - } - }); - } - - - function compileTemplateUrl(directives, beforeTemplateNodeLinkFn, $compileNode, tAttrs, - $rootElement, replace, childTranscludeFn) { - var linkQueue = [], - afterTemplateNodeLinkFn, - afterTemplateChildLinkFn, - beforeTemplateCompileNode = $compileNode[0], - origAsyncDirective = directives.shift(), - // The fact that we have to copy and patch the directive seems wrong! - derivedSyncDirective = extend({}, origAsyncDirective, { - controller: null, templateUrl: null, transclude: null, scope: null - }); - - $compileNode.html(''); - - $http.get(origAsyncDirective.templateUrl, {cache: $templateCache}). - success(function(content) { - var compileNode, tempTemplateAttrs, $template; - - content = denormalizeTemplate(content); - - if (replace) { - $template = jqLite('
' + trim(content) + '
').contents(); - compileNode = $template[0]; - - if ($template.length != 1 || compileNode.nodeType !== 1) { - throw new Error(MULTI_ROOT_TEMPLATE_ERROR + content); - } - - tempTemplateAttrs = {$attr: {}}; - replaceWith($rootElement, $compileNode, compileNode); - collectDirectives(compileNode, directives, tempTemplateAttrs); - mergeTemplateAttributes(tAttrs, tempTemplateAttrs); - } else { - compileNode = beforeTemplateCompileNode; - $compileNode.html(content); - } - - directives.unshift(derivedSyncDirective); - afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, childTranscludeFn); - afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); - - - while(linkQueue.length) { - var controller = linkQueue.pop(), - linkRootElement = linkQueue.pop(), - beforeTemplateLinkNode = linkQueue.pop(), - scope = linkQueue.pop(), - linkNode = compileNode; - - if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { - // it was cloned therefore we have to clone as well. - linkNode = JQLiteClone(compileNode); - replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); - } - - afterTemplateNodeLinkFn(function() { - beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, controller); - }, scope, linkNode, $rootElement, controller); - } - linkQueue = null; - }). - error(function(response, code, headers, config) { - throw Error('Failed to load template: ' + config.url); - }); - - return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, controller) { - if (linkQueue) { - linkQueue.push(scope); - linkQueue.push(node); - linkQueue.push(rootElement); - linkQueue.push(controller); - } else { - afterTemplateNodeLinkFn(function() { - beforeTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, controller); - }, scope, node, rootElement, controller); - } - }; - } - - - /** - * Sorting function for bound directives. - */ - function byPriority(a, b) { - return b.priority - a.priority; - } - - - function assertNoDuplicate(what, previousDirective, directive, element) { - if (previousDirective) { - throw Error('Multiple directives [' + previousDirective.name + ', ' + - directive.name + '] asking for ' + what + ' on: ' + startingTag(element)); - } - } - - - function addTextInterpolateDirective(directives, text) { - var interpolateFn = $interpolate(text, true); - if (interpolateFn) { - directives.push({ - priority: 0, - compile: valueFn(function textInterpolateLinkFn(scope, node) { - var parent = node.parent(), - bindings = parent.data('$binding') || []; - bindings.push(interpolateFn); - safeAddClass(parent.data('$binding', bindings), 'ng-binding'); - scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { - node[0].nodeValue = value; - }); - }) - }); - } - } - - - function addAttrInterpolateDirective(node, directives, value, name) { - var interpolateFn = $interpolate(value, true); - - // no interpolation found -> ignore - if (!interpolateFn) return; - - - directives.push({ - priority: 100, - compile: valueFn(function attrInterpolateLinkFn(scope, element, attr) { - var $$observers = (attr.$$observers || (attr.$$observers = {})); - - if (name === 'class') { - // we need to interpolate classes again, in the case the element was replaced - // and therefore the two class attrs got merged - we want to interpolate the result - interpolateFn = $interpolate(attr[name], true); - } - - attr[name] = undefined; - ($$observers[name] || ($$observers[name] = [])).$$inter = true; - (attr.$$observers && attr.$$observers[name].$$scope || scope). - $watch(interpolateFn, function interpolateFnWatchAction(value) { - attr.$set(name, value); - }); - }) - }); - } - - - /** - * This is a special jqLite.replaceWith, which can replace items which - * have no parents, provided that the containing jqLite collection is provided. - * - * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes - * in the root of the tree. - * @param {JqLite} $element The jqLite element which we are going to replace. We keep the shell, - * but replace its DOM node reference. - * @param {Node} newNode The new DOM node. - */ - function replaceWith($rootElement, $element, newNode) { - var oldNode = $element[0], - parent = oldNode.parentNode, - i, ii; - - if ($rootElement) { - for(i = 0, ii = $rootElement.length; i < ii; i++) { - if ($rootElement[i] == oldNode) { - $rootElement[i] = newNode; - break; - } - } - } - - if (parent) { - parent.replaceChild(newNode, oldNode); - } - - newNode[jqLite.expando] = oldNode[jqLite.expando]; - $element[0] = newNode; - } - }]; -} - -var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; -/** - * Converts all accepted directives format into proper directive name. - * All of these will become 'myDirective': - * my:DiRective - * my-directive - * x-my-directive - * data-my:directive - * - * Also there is special case for Moz prefix starting with upper case letter. - * @param name Name to normalize - */ -function directiveNormalize(name) { - return camelCase(name.replace(PREFIX_REGEXP, '')); -} - -/** - * @ngdoc object - * @name ng.$compile.directive.Attributes - * @description - * - * A shared object between directive compile / linking functions which contains normalized DOM element - * attributes. The the values reflect current binding state `{{ }}`. The normalization is needed - * since all of these are treated as equivalent in Angular: - * - * - */ - -/** - * @ngdoc property - * @name ng.$compile.directive.Attributes#$attr - * @propertyOf ng.$compile.directive.Attributes - * @returns {object} A map of DOM element attribute names to the normalized name. This is - * needed to do reverse lookup from normalized name back to actual name. - */ - - -/** - * @ngdoc function - * @name ng.$compile.directive.Attributes#$set - * @methodOf ng.$compile.directive.Attributes - * @function - * - * @description - * Set DOM element attribute value. - * - * - * @param {string} name Normalized element attribute name of the property to modify. The name is - * revers translated using the {@link ng.$compile.directive.Attributes#$attr $attr} - * property to the original name. - * @param {string} value Value to set the attribute to. - */ - - - -/** - * Closure compiler type information - */ - -function nodesetLinkingFn( - /* angular.Scope */ scope, - /* NodeList */ nodeList, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -){} - -function directiveLinkingFn( - /* nodesetLinkingFn */ nodesetLinkingFn, - /* angular.Scope */ scope, - /* Node */ node, - /* Element */ rootElement, - /* function(Function) */ boundTranscludeFn -){} - -/** - * @ngdoc object - * @name ng.$controllerProvider - * @description - * The {@link ng.$controller $controller service} is used by Angular to create new - * controllers. - * - * This provider allows controller registration via the - * {@link ng.$controllerProvider#register register} method. - */ -function $ControllerProvider() { - var controllers = {}; - - - /** - * @ngdoc function - * @name ng.$controllerProvider#register - * @methodOf ng.$controllerProvider - * @param {string} name Controller name - * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI - * annotations in the array notation). - */ - this.register = function(name, constructor) { - if (isObject(name)) { - extend(controllers, name) - } else { - controllers[name] = constructor; - } - }; - - - this.$get = ['$injector', '$window', function($injector, $window) { - - /** - * @ngdoc function - * @name ng.$controller - * @requires $injector - * - * @param {Function|string} constructor If called with a function then it's considered to be the - * controller constructor function. Otherwise it's considered to be a string which is used - * to retrieve the controller constructor using the following steps: - * - * * check if a controller with given name is registered via `$controllerProvider` - * * check if evaluating the string on the current scope returns a constructor - * * check `window[constructor]` on the global `window` object - * - * @param {Object} locals Injection locals for Controller. - * @return {Object} Instance of given controller. - * - * @description - * `$controller` service is responsible for instantiating controllers. - * - * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into - * a service, so that one can override this service with {@link https://gist.github.com/1649788 - * BC version}. - */ - return function(constructor, locals) { - if(isString(constructor)) { - var name = constructor; - constructor = controllers.hasOwnProperty(name) - ? controllers[name] - : getter(locals.$scope, name, true) || getter($window, name, true); - - assertArgFn(constructor, name, true); - } - - return $injector.instantiate(constructor, locals); - }; - }]; -} - -/** - * @ngdoc object - * @name ng.$document - * @requires $window - * - * @description - * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` - * element. - */ -function $DocumentProvider(){ - this.$get = ['$window', function(window){ - return jqLite(window.document); - }]; -} - -/** - * @ngdoc function - * @name ng.$exceptionHandler - * @requires $log - * - * @description - * Any uncaught exception in angular expressions is delegated to this service. - * The default implementation simply delegates to `$log.error` which logs it into - * the browser console. - * - * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by - * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. - * - * @param {Error} exception Exception associated with the error. - * @param {string=} cause optional information about the context in which - * the error was thrown. - * - */ -function $ExceptionHandlerProvider() { - this.$get = ['$log', function($log) { - return function(exception, cause) { - $log.error.apply($log, arguments); - }; - }]; -} - -/** - * @ngdoc object - * @name ng.$interpolateProvider - * @function - * - * @description - * - * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. - */ -function $InterpolateProvider() { - var startSymbol = '{{'; - var endSymbol = '}}'; - - /** - * @ngdoc method - * @name ng.$interpolateProvider#startSymbol - * @methodOf ng.$interpolateProvider - * @description - * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. - * - * @param {string=} value new value to set the starting symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.startSymbol = function(value){ - if (value) { - startSymbol = value; - return this; - } else { - return startSymbol; - } - }; - - /** - * @ngdoc method - * @name ng.$interpolateProvider#endSymbol - * @methodOf ng.$interpolateProvider - * @description - * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. - * - * @param {string=} value new value to set the ending symbol to. - * @returns {string|self} Returns the symbol when used as getter and self if used as setter. - */ - this.endSymbol = function(value){ - if (value) { - endSymbol = value; - return this; - } else { - return endSymbol; - } - }; - - - this.$get = ['$parse', function($parse) { - var startSymbolLength = startSymbol.length, - endSymbolLength = endSymbol.length; - - /** - * @ngdoc function - * @name ng.$interpolate - * @function - * - * @requires $parse - * - * @description - * - * Compiles a string with markup into an interpolation function. This service is used by the - * HTML {@link ng.$compile $compile} service for data binding. See - * {@link ng.$interpolateProvider $interpolateProvider} for configuring the - * interpolation markup. - * - * -
-         var $interpolate = ...; // injected
-         var exp = $interpolate('Hello {{name}}!');
-         expect(exp({name:'Angular'}).toEqual('Hello Angular!');
-       
- * - * - * @param {string} text The text with markup to interpolate. - * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have - * embedded expression in order to return an interpolation function. Strings with no - * embedded expression will return null for the interpolation function. - * @returns {function(context)} an interpolation function which is used to compute the interpolated - * string. The function has these parameters: - * - * * `context`: an object against which any expressions embedded in the strings are evaluated - * against. - * - */ - function $interpolate(text, mustHaveExpression) { - var startIndex, - endIndex, - index = 0, - parts = [], - length = text.length, - hasInterpolation = false, - fn, - exp, - concat = []; - - while(index < length) { - if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && - ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { - (index != startIndex) && parts.push(text.substring(index, startIndex)); - parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); - fn.exp = exp; - index = endIndex + endSymbolLength; - hasInterpolation = true; - } else { - // we did not find anything, so we have to add the remainder to the parts array - (index != length) && parts.push(text.substring(index)); - index = length; - } - } - - if (!(length = parts.length)) { - // we added, nothing, must have been an empty string. - parts.push(''); - length = 1; - } - - if (!mustHaveExpression || hasInterpolation) { - concat.length = length; - fn = function(context) { - for(var i = 0, ii = length, part; i html5 url - } else { - return composeProtocolHostPort(match.protocol, match.host, match.port) + - pathPrefixFromBase(basePath) + match.hash.substr(hashPrefix.length); - } -} - - -function convertToHashbangUrl(url, basePath, hashPrefix) { - var match = matchUrl(url); - - // already hashbang url - if (decodeURIComponent(match.path) == basePath && !isUndefined(match.hash) && - match.hash.indexOf(hashPrefix) === 0) { - return url; - // convert html5 url -> hashbang url - } else { - var search = match.search && '?' + match.search || '', - hash = match.hash && '#' + match.hash || '', - pathPrefix = pathPrefixFromBase(basePath), - path = match.path.substr(pathPrefix.length); - - if (match.path.indexOf(pathPrefix) !== 0) { - throw Error('Invalid url "' + url + '", missing path prefix "' + pathPrefix + '" !'); - } - - return composeProtocolHostPort(match.protocol, match.host, match.port) + basePath + - '#' + hashPrefix + path + search + hash; - } -} - - -/** - * LocationUrl represents an url - * This object is exposed as $location service when HTML5 mode is enabled and supported - * - * @constructor - * @param {string} url HTML5 url - * @param {string} pathPrefix - */ -function LocationUrl(url, pathPrefix, appBaseUrl) { - pathPrefix = pathPrefix || ''; - - /** - * Parse given html5 (regular) url string into properties - * @param {string} newAbsoluteUrl HTML5 url - * @private - */ - this.$$parse = function(newAbsoluteUrl) { - var match = matchUrl(newAbsoluteUrl, this); - - if (match.path.indexOf(pathPrefix) !== 0) { - throw Error('Invalid url "' + newAbsoluteUrl + '", missing path prefix "' + pathPrefix + '" !'); - } - - this.$$path = decodeURIComponent(match.path.substr(pathPrefix.length)); - this.$$search = parseKeyValue(match.search); - this.$$hash = match.hash && decodeURIComponent(match.hash) || ''; - - this.$$compose(); - }; - - /** - * Compose url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) + - pathPrefix + this.$$url; - }; - - - this.$$rewriteAppUrl = function(absoluteLinkUrl) { - if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) { - return absoluteLinkUrl; - } - } - - - this.$$parse(url); -} - - -/** - * LocationHashbangUrl represents url - * This object is exposed as $location service when html5 history api is disabled or not supported - * - * @constructor - * @param {string} url Legacy url - * @param {string} hashPrefix Prefix for hash part (containing path and search) - */ -function LocationHashbangUrl(url, hashPrefix, appBaseUrl) { - var basePath; - - /** - * Parse given hashbang url into properties - * @param {string} url Hashbang url - * @private - */ - this.$$parse = function(url) { - var match = matchUrl(url, this); - - - if (match.hash && match.hash.indexOf(hashPrefix) !== 0) { - throw Error('Invalid url "' + url + '", missing hash prefix "' + hashPrefix + '" !'); - } - - basePath = match.path + (match.search ? '?' + match.search : ''); - match = HASH_MATCH.exec((match.hash || '').substr(hashPrefix.length)); - if (match[1]) { - this.$$path = (match[1].charAt(0) == '/' ? '' : '/') + decodeURIComponent(match[1]); - } else { - this.$$path = ''; - } - - this.$$search = parseKeyValue(match[3]); - this.$$hash = match[5] && decodeURIComponent(match[5]) || ''; - - this.$$compose(); - }; - - /** - * Compose hashbang url and update `absUrl` property - * @private - */ - this.$$compose = function() { - var search = toKeyValue(this.$$search), - hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; - - this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; - this.$$absUrl = composeProtocolHostPort(this.$$protocol, this.$$host, this.$$port) + - basePath + (this.$$url ? '#' + hashPrefix + this.$$url : ''); - }; - - this.$$rewriteAppUrl = function(absoluteLinkUrl) { - if(absoluteLinkUrl.indexOf(appBaseUrl) == 0) { - return absoluteLinkUrl; - } - } - - - this.$$parse(url); -} - - -LocationUrl.prototype = { - - /** - * Has any change been replacing ? - * @private - */ - $$replace: false, - - /** - * @ngdoc method - * @name ng.$location#absUrl - * @methodOf ng.$location - * - * @description - * This method is getter only. - * - * Return full url representation with all segments encoded according to rules specified in - * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. - * - * @return {string} full url - */ - absUrl: locationGetter('$$absUrl'), - - /** - * @ngdoc method - * @name ng.$location#url - * @methodOf ng.$location - * - * @description - * This method is getter / setter. - * - * Return url (e.g. `/path?a=b#hash`) when called without any parameter. - * - * Change path, search and hash, when called with parameter and return `$location`. - * - * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) - * @return {string} url - */ - url: function(url, replace) { - if (isUndefined(url)) - return this.$$url; - - var match = PATH_MATCH.exec(url); - if (match[1]) this.path(decodeURIComponent(match[1])); - if (match[2] || match[1]) this.search(match[3] || ''); - this.hash(match[5] || '', replace); - - return this; - }, - - /** - * @ngdoc method - * @name ng.$location#protocol - * @methodOf ng.$location - * - * @description - * This method is getter only. - * - * Return protocol of current url. - * - * @return {string} protocol of current url - */ - protocol: locationGetter('$$protocol'), - - /** - * @ngdoc method - * @name ng.$location#host - * @methodOf ng.$location - * - * @description - * This method is getter only. - * - * Return host of current url. - * - * @return {string} host of current url. - */ - host: locationGetter('$$host'), - - /** - * @ngdoc method - * @name ng.$location#port - * @methodOf ng.$location - * - * @description - * This method is getter only. - * - * Return port of current url. - * - * @return {Number} port - */ - port: locationGetter('$$port'), - - /** - * @ngdoc method - * @name ng.$location#path - * @methodOf ng.$location - * - * @description - * This method is getter / setter. - * - * Return path of current url when called without any parameter. - * - * Change path when called with parameter and return `$location`. - * - * Note: Path should always begin with forward slash (/), this method will add the forward slash - * if it is missing. - * - * @param {string=} path New path - * @return {string} path - */ - path: locationGetterSetter('$$path', function(path) { - return path.charAt(0) == '/' ? path : '/' + path; - }), - - /** - * @ngdoc method - * @name ng.$location#search - * @methodOf ng.$location - * - * @description - * This method is getter / setter. - * - * Return search part (as object) of current url when called without any parameter. - * - * Change search part when called with parameter and return `$location`. - * - * @param {string|object=} search New search params - string or hash object - * @param {string=} paramValue If `search` is a string, then `paramValue` will override only a - * single search parameter. If the value is `null`, the parameter will be deleted. - * - * @return {string} search - */ - search: function(search, paramValue) { - if (isUndefined(search)) - return this.$$search; - - if (isDefined(paramValue)) { - if (paramValue === null) { - delete this.$$search[search]; - } else { - this.$$search[search] = paramValue; - } - } else { - this.$$search = isString(search) ? parseKeyValue(search) : search; - } - - this.$$compose(); - return this; - }, - - /** - * @ngdoc method - * @name ng.$location#hash - * @methodOf ng.$location - * - * @description - * This method is getter / setter. - * - * Return hash fragment when called without any parameter. - * - * Change hash fragment when called with parameter and return `$location`. - * - * @param {string=} hash New hash fragment - * @return {string} hash - */ - hash: locationGetterSetter('$$hash', identity), - - /** - * @ngdoc method - * @name ng.$location#replace - * @methodOf ng.$location - * - * @description - * If called, all changes to $location during current `$digest` will be replacing current history - * record, instead of adding new one. - */ - replace: function() { - this.$$replace = true; - return this; - } -}; - -LocationHashbangUrl.prototype = inherit(LocationUrl.prototype); - -function LocationHashbangInHtml5Url(url, hashPrefix, appBaseUrl, baseExtra) { - LocationHashbangUrl.apply(this, arguments); - - - this.$$rewriteAppUrl = function(absoluteLinkUrl) { - if (absoluteLinkUrl.indexOf(appBaseUrl) == 0) { - return appBaseUrl + baseExtra + '#' + hashPrefix + absoluteLinkUrl.substr(appBaseUrl.length); - } - } -} - -LocationHashbangInHtml5Url.prototype = inherit(LocationHashbangUrl.prototype); - -function locationGetter(property) { - return function() { - return this[property]; - }; -} - - -function locationGetterSetter(property, preprocess) { - return function(value) { - if (isUndefined(value)) - return this[property]; - - this[property] = preprocess(value); - this.$$compose(); - - return this; - }; -} - - -/** - * @ngdoc object - * @name ng.$location - * - * @requires $browser - * @requires $sniffer - * @requires $rootElement - * - * @description - * The $location service parses the URL in the browser address bar (based on the - * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL - * available to your application. Changes to the URL in the address bar are reflected into - * $location service and changes to $location are reflected into the browser address bar. - * - * **The $location service:** - * - * - Exposes the current URL in the browser address bar, so you can - * - Watch and observe the URL. - * - Change the URL. - * - Synchronizes the URL with the browser when the user - * - Changes the address bar. - * - Clicks the back or forward button (or clicks a History link). - * - Clicks on a link. - * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). - * - * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular - * Services: Using $location} - */ - -/** - * @ngdoc object - * @name ng.$locationProvider - * @description - * Use the `$locationProvider` to configure how the application deep linking paths are stored. - */ -function $LocationProvider(){ - var hashPrefix = '', - html5Mode = false; - - /** - * @ngdoc property - * @name ng.$locationProvider#hashPrefix - * @methodOf ng.$locationProvider - * @description - * @param {string=} prefix Prefix for hash part (containing path and search) - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.hashPrefix = function(prefix) { - if (isDefined(prefix)) { - hashPrefix = prefix; - return this; - } else { - return hashPrefix; - } - }; - - /** - * @ngdoc property - * @name ng.$locationProvider#html5Mode - * @methodOf ng.$locationProvider - * @description - * @param {string=} mode Use HTML5 strategy if available. - * @returns {*} current value if used as getter or itself (chaining) if used as setter - */ - this.html5Mode = function(mode) { - if (isDefined(mode)) { - html5Mode = mode; - return this; - } else { - return html5Mode; - } - }; - - this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', - function( $rootScope, $browser, $sniffer, $rootElement) { - var $location, - basePath, - pathPrefix, - initUrl = $browser.url(), - initUrlParts = matchUrl(initUrl), - appBaseUrl; - - if (html5Mode) { - basePath = $browser.baseHref() || '/'; - pathPrefix = pathPrefixFromBase(basePath); - appBaseUrl = - composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) + - pathPrefix + '/'; - - if ($sniffer.history) { - $location = new LocationUrl( - convertToHtml5Url(initUrl, basePath, hashPrefix), - pathPrefix, appBaseUrl); - } else { - $location = new LocationHashbangInHtml5Url( - convertToHashbangUrl(initUrl, basePath, hashPrefix), - hashPrefix, appBaseUrl, basePath.substr(pathPrefix.length + 1)); - } - } else { - appBaseUrl = - composeProtocolHostPort(initUrlParts.protocol, initUrlParts.host, initUrlParts.port) + - (initUrlParts.path || '') + - (initUrlParts.search ? ('?' + initUrlParts.search) : '') + - '#' + hashPrefix + '/'; - - $location = new LocationHashbangUrl(initUrl, hashPrefix, appBaseUrl); - } - - $rootElement.bind('click', function(event) { - // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) - // currently we open nice url link and redirect then - - if (event.ctrlKey || event.metaKey || event.which == 2) return; - - var elm = jqLite(event.target); - - // traverse the DOM up to find first A tag - while (lowercase(elm[0].nodeName) !== 'a') { - // ignore rewriting if no A tag (reached root element, or no parent - removed from document) - if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; - } - - var absHref = elm.prop('href'), - rewrittenUrl = $location.$$rewriteAppUrl(absHref); - - if (absHref && !elm.attr('target') && rewrittenUrl) { - // update location manually - $location.$$parse(rewrittenUrl); - $rootScope.$apply(); - event.preventDefault(); - // hack to work around FF6 bug 684208 when scenario runner clicks on links - window.angular['ff-684208-preventDefault'] = true; - } - }); - - - // rewrite hashbang url <> html5 url - if ($location.absUrl() != initUrl) { - $browser.url($location.absUrl(), true); - } - - // update $location when $browser url changes - $browser.onUrlChange(function(newUrl) { - if ($location.absUrl() != newUrl) { - if ($rootScope.$broadcast('$locationChangeStart', newUrl, $location.absUrl()).defaultPrevented) { - $browser.url($location.absUrl()); - return; - } - $rootScope.$evalAsync(function() { - var oldUrl = $location.absUrl(); - - $location.$$parse(newUrl); - afterLocationChange(oldUrl); - }); - if (!$rootScope.$$phase) $rootScope.$digest(); - } - }); - - // update browser - var changeCounter = 0; - $rootScope.$watch(function $locationWatch() { - var oldUrl = $browser.url(); - var currentReplace = $location.$$replace; - - if (!changeCounter || oldUrl != $location.absUrl()) { - changeCounter++; - $rootScope.$evalAsync(function() { - if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). - defaultPrevented) { - $location.$$parse(oldUrl); - } else { - $browser.url($location.absUrl(), currentReplace); - afterLocationChange(oldUrl); - } - }); - } - $location.$$replace = false; - - return changeCounter; - }); - - return $location; - - function afterLocationChange(oldUrl) { - $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); - } -}]; -} - -/** - * @ngdoc object - * @name ng.$log - * @requires $window - * - * @description - * Simple service for logging. Default implementation writes the message - * into the browser's console (if present). - * - * The main purpose of this service is to simplify debugging and troubleshooting. - * - * @example - - - function LogCtrl($scope, $log) { - $scope.$log = $log; - $scope.message = 'Hello World!'; - } - - -
-

Reload this page with open console, enter text and hit the log button...

- Message: - - - - - -
-
-
- */ - -function $LogProvider(){ - this.$get = ['$window', function($window){ - return { - /** - * @ngdoc method - * @name ng.$log#log - * @methodOf ng.$log - * - * @description - * Write a log message - */ - log: consoleLog('log'), - - /** - * @ngdoc method - * @name ng.$log#warn - * @methodOf ng.$log - * - * @description - * Write a warning message - */ - warn: consoleLog('warn'), - - /** - * @ngdoc method - * @name ng.$log#info - * @methodOf ng.$log - * - * @description - * Write an information message - */ - info: consoleLog('info'), - - /** - * @ngdoc method - * @name ng.$log#error - * @methodOf ng.$log - * - * @description - * Write an error message - */ - error: consoleLog('error') - }; - - function formatError(arg) { - if (arg instanceof Error) { - if (arg.stack) { - arg = (arg.message && arg.stack.indexOf(arg.message) === -1) - ? 'Error: ' + arg.message + '\n' + arg.stack - : arg.stack; - } else if (arg.sourceURL) { - arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; - } - } - return arg; - } - - function consoleLog(type) { - var console = $window.console || {}, - logFn = console[type] || console.log || noop; - - if (logFn.apply) { - return function() { - var args = []; - forEach(arguments, function(arg) { - args.push(formatError(arg)); - }); - return logFn.apply(console, args); - }; - } - - // we are IE which either doesn't have window.console => this is noop and we do nothing, - // or we are IE where console.log doesn't have apply so we log at least first 2 args - return function(arg1, arg2) { - logFn(arg1, arg2); - } - } - }]; -} - -var OPERATORS = { - 'null':function(){return null;}, - 'true':function(){return true;}, - 'false':function(){return false;}, - undefined:noop, - '+':function(self, locals, a,b){ - a=a(self, locals); b=b(self, locals); - if (isDefined(a)) { - if (isDefined(b)) { - return a + b; - } - return a; - } - return isDefined(b)?b:undefined;}, - '-':function(self, locals, a,b){a=a(self, locals); b=b(self, locals); return (isDefined(a)?a:0)-(isDefined(b)?b:0);}, - '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, - '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, - '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, - '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, - '=':noop, - '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, - '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, - '<':function(self, locals, a,b){return a(self, locals)':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, - '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, - '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, - '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, - '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, - '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, -// '|':function(self, locals, a,b){return a|b;}, - '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, - '!':function(self, locals, a){return !a(self, locals);} -}; -var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; - -function lex(text, csp){ - var tokens = [], - token, - index = 0, - json = [], - ch, - lastCh = ':'; // can start regexp - - while (index < text.length) { - ch = text.charAt(index); - if (is('"\'')) { - readString(ch); - } else if (isNumber(ch) || is('.') && isNumber(peek())) { - readNumber(); - } else if (isIdent(ch)) { - readIdent(); - // identifiers can only be if the preceding char was a { or , - if (was('{,') && json[0]=='{' && - (token=tokens[tokens.length-1])) { - token.json = token.text.indexOf('.') == -1; - } - } else if (is('(){}[].,;:')) { - tokens.push({ - index:index, - text:ch, - json:(was(':[,') && is('{[')) || is('}]:,') - }); - if (is('{[')) json.unshift(ch); - if (is('}]')) json.shift(); - index++; - } else if (isWhitespace(ch)) { - index++; - continue; - } else { - var ch2 = ch + peek(), - fn = OPERATORS[ch], - fn2 = OPERATORS[ch2]; - if (fn2) { - tokens.push({index:index, text:ch2, fn:fn2}); - index += 2; - } else if (fn) { - tokens.push({index:index, text:ch, fn:fn, json: was('[,:') && is('+-')}); - index += 1; - } else { - throwError("Unexpected next character ", index, index+1); - } - } - lastCh = ch; - } - return tokens; - - function is(chars) { - return chars.indexOf(ch) != -1; - } - - function was(chars) { - return chars.indexOf(lastCh) != -1; - } - - function peek() { - return index + 1 < text.length ? text.charAt(index + 1) : false; - } - function isNumber(ch) { - return '0' <= ch && ch <= '9'; - } - function isWhitespace(ch) { - return ch == ' ' || ch == '\r' || ch == '\t' || - ch == '\n' || ch == '\v' || ch == '\u00A0'; // IE treats non-breaking space as \u00A0 - } - function isIdent(ch) { - return 'a' <= ch && ch <= 'z' || - 'A' <= ch && ch <= 'Z' || - '_' == ch || ch == '$'; - } - function isExpOperator(ch) { - return ch == '-' || ch == '+' || isNumber(ch); - } - - function throwError(error, start, end) { - end = end || index; - throw Error("Lexer Error: " + error + " at column" + - (isDefined(start) - ? "s " + start + "-" + index + " [" + text.substring(start, end) + "]" - : " " + end) + - " in expression [" + text + "]."); - } - - function readNumber() { - var number = ""; - var start = index; - while (index < text.length) { - var ch = lowercase(text.charAt(index)); - if (ch == '.' || isNumber(ch)) { - number += ch; - } else { - var peekCh = peek(); - if (ch == 'e' && isExpOperator(peekCh)) { - number += ch; - } else if (isExpOperator(ch) && - peekCh && isNumber(peekCh) && - number.charAt(number.length - 1) == 'e') { - number += ch; - } else if (isExpOperator(ch) && - (!peekCh || !isNumber(peekCh)) && - number.charAt(number.length - 1) == 'e') { - throwError('Invalid exponent'); - } else { - break; - } - } - index++; - } - number = 1 * number; - tokens.push({index:start, text:number, json:true, - fn:function() {return number;}}); - } - function readIdent() { - var ident = "", - start = index, - lastDot, peekIndex, methodName, ch; - - while (index < text.length) { - ch = text.charAt(index); - if (ch == '.' || isIdent(ch) || isNumber(ch)) { - if (ch == '.') lastDot = index; - ident += ch; - } else { - break; - } - index++; - } - - //check if this is not a method invocation and if it is back out to last dot - if (lastDot) { - peekIndex = index; - while(peekIndex < text.length) { - ch = text.charAt(peekIndex); - if (ch == '(') { - methodName = ident.substr(lastDot - start + 1); - ident = ident.substr(0, lastDot - start); - index = peekIndex; - break; - } - if(isWhitespace(ch)) { - peekIndex++; - } else { - break; - } - } - } - - - var token = { - index:start, - text:ident - }; - - if (OPERATORS.hasOwnProperty(ident)) { - token.fn = token.json = OPERATORS[ident]; - } else { - var getter = getterFn(ident, csp); - token.fn = extend(function(self, locals) { - return (getter(self, locals)); - }, { - assign: function(self, value) { - return setter(self, ident, value); - } - }); - } - - tokens.push(token); - - if (methodName) { - tokens.push({ - index:lastDot, - text: '.', - json: false - }); - tokens.push({ - index: lastDot + 1, - text: methodName, - json: false - }); - } - } - - function readString(quote) { - var start = index; - index++; - var string = ""; - var rawString = quote; - var escape = false; - while (index < text.length) { - var ch = text.charAt(index); - rawString += ch; - if (escape) { - if (ch == 'u') { - var hex = text.substring(index + 1, index + 5); - if (!hex.match(/[\da-f]{4}/i)) - throwError( "Invalid unicode escape [\\u" + hex + "]"); - index += 4; - string += String.fromCharCode(parseInt(hex, 16)); - } else { - var rep = ESCAPE[ch]; - if (rep) { - string += rep; - } else { - string += ch; - } - } - escape = false; - } else if (ch == '\\') { - escape = true; - } else if (ch == quote) { - index++; - tokens.push({ - index:start, - text:rawString, - string:string, - json:true, - fn:function() { return string; } - }); - return; - } else { - string += ch; - } - index++; - } - throwError("Unterminated quote", start); - } -} - -///////////////////////////////////////// - -function parser(text, json, $filter, csp){ - var ZERO = valueFn(0), - value, - tokens = lex(text, csp), - assignment = _assignment, - functionCall = _functionCall, - fieldAccess = _fieldAccess, - objectIndex = _objectIndex, - filterChain = _filterChain; - - if(json){ - // The extra level of aliasing is here, just in case the lexer misses something, so that - // we prevent any accidental execution in JSON. - assignment = logicalOR; - functionCall = - fieldAccess = - objectIndex = - filterChain = - function() { throwError("is not valid json", {text:text, index:0}); }; - value = primary(); - } else { - value = statements(); - } - if (tokens.length !== 0) { - throwError("is an unexpected token", tokens[0]); - } - return value; - - /////////////////////////////////// - function throwError(msg, token) { - throw Error("Syntax Error: Token '" + token.text + - "' " + msg + " at column " + - (token.index + 1) + " of the expression [" + - text + "] starting at [" + text.substring(token.index) + "]."); - } - - function peekToken() { - if (tokens.length === 0) - throw Error("Unexpected end of expression: " + text); - return tokens[0]; - } - - function peek(e1, e2, e3, e4) { - if (tokens.length > 0) { - var token = tokens[0]; - var t = token.text; - if (t==e1 || t==e2 || t==e3 || t==e4 || - (!e1 && !e2 && !e3 && !e4)) { - return token; - } - } - return false; - } - - function expect(e1, e2, e3, e4){ - var token = peek(e1, e2, e3, e4); - if (token) { - if (json && !token.json) { - throwError("is not valid json", token); - } - tokens.shift(); - return token; - } - return false; - } - - function consume(e1){ - if (!expect(e1)) { - throwError("is unexpected, expecting [" + e1 + "]", peek()); - } - } - - function unaryFn(fn, right) { - return function(self, locals) { - return fn(self, locals, right); - }; - } - - function binaryFn(left, fn, right) { - return function(self, locals) { - return fn(self, locals, left, right); - }; - } - - function statements() { - var statements = []; - while(true) { - if (tokens.length > 0 && !peek('}', ')', ';', ']')) - statements.push(filterChain()); - if (!expect(';')) { - // optimize for the common case where there is only one statement. - // TODO(size): maybe we should not support multiple statements? - return statements.length == 1 - ? statements[0] - : function(self, locals){ - var value; - for ( var i = 0; i < statements.length; i++) { - var statement = statements[i]; - if (statement) - value = statement(self, locals); - } - return value; - }; - } - } - } - - function _filterChain() { - var left = expression(); - var token; - while(true) { - if ((token = expect('|'))) { - left = binaryFn(left, token.fn, filter()); - } else { - return left; - } - } - } - - function filter() { - var token = expect(); - var fn = $filter(token.text); - var argsFn = []; - while(true) { - if ((token = expect(':'))) { - argsFn.push(expression()); - } else { - var fnInvoke = function(self, locals, input){ - var args = [input]; - for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](self, locals)); - } - return fn.apply(self, args); - }; - return function() { - return fnInvoke; - }; - } - } - } - - function expression() { - return assignment(); - } - - function _assignment() { - var left = logicalOR(); - var right; - var token; - if ((token = expect('='))) { - if (!left.assign) { - throwError("implies assignment but [" + - text.substring(0, token.index) + "] can not be assigned to", token); - } - right = logicalOR(); - return function(scope, locals){ - return left.assign(scope, right(scope, locals), locals); - }; - } else { - return left; - } - } - - function logicalOR() { - var left = logicalAND(); - var token; - while(true) { - if ((token = expect('||'))) { - left = binaryFn(left, token.fn, logicalAND()); - } else { - return left; - } - } - } - - function logicalAND() { - var left = equality(); - var token; - if ((token = expect('&&'))) { - left = binaryFn(left, token.fn, logicalAND()); - } - return left; - } - - function equality() { - var left = relational(); - var token; - if ((token = expect('==','!='))) { - left = binaryFn(left, token.fn, equality()); - } - return left; - } - - function relational() { - var left = additive(); - var token; - if ((token = expect('<', '>', '<=', '>='))) { - left = binaryFn(left, token.fn, relational()); - } - return left; - } - - function additive() { - var left = multiplicative(); - var token; - while ((token = expect('+','-'))) { - left = binaryFn(left, token.fn, multiplicative()); - } - return left; - } - - function multiplicative() { - var left = unary(); - var token; - while ((token = expect('*','/','%'))) { - left = binaryFn(left, token.fn, unary()); - } - return left; - } - - function unary() { - var token; - if (expect('+')) { - return primary(); - } else if ((token = expect('-'))) { - return binaryFn(ZERO, token.fn, unary()); - } else if ((token = expect('!'))) { - return unaryFn(token.fn, unary()); - } else { - return primary(); - } - } - - - function primary() { - var primary; - if (expect('(')) { - primary = filterChain(); - consume(')'); - } else if (expect('[')) { - primary = arrayDeclaration(); - } else if (expect('{')) { - primary = object(); - } else { - var token = expect(); - primary = token.fn; - if (!primary) { - throwError("not a primary expression", token); - } - } - - var next, context; - while ((next = expect('(', '[', '.'))) { - if (next.text === '(') { - primary = functionCall(primary, context); - context = null; - } else if (next.text === '[') { - context = primary; - primary = objectIndex(primary); - } else if (next.text === '.') { - context = primary; - primary = fieldAccess(primary); - } else { - throwError("IMPOSSIBLE"); - } - } - return primary; - } - - function _fieldAccess(object) { - var field = expect().text; - var getter = getterFn(field, csp); - return extend( - function(scope, locals, self) { - return getter(self || object(scope, locals), locals); - }, - { - assign:function(scope, value, locals) { - return setter(object(scope, locals), field, value); - } - } - ); - } - - function _objectIndex(obj) { - var indexFn = expression(); - consume(']'); - return extend( - function(self, locals){ - var o = obj(self, locals), - i = indexFn(self, locals), - v, p; - - if (!o) return undefined; - v = o[i]; - if (v && v.then) { - p = v; - if (!('$$v' in v)) { - p.$$v = undefined; - p.then(function(val) { p.$$v = val; }); - } - v = v.$$v; - } - return v; - }, { - assign:function(self, value, locals){ - return obj(self, locals)[indexFn(self, locals)] = value; - } - }); - } - - function _functionCall(fn, contextGetter) { - var argsFn = []; - if (peekToken().text != ')') { - do { - argsFn.push(expression()); - } while (expect(',')); - } - consume(')'); - return function(scope, locals){ - var args = [], - context = contextGetter ? contextGetter(scope, locals) : scope; - - for ( var i = 0; i < argsFn.length; i++) { - args.push(argsFn[i](scope, locals)); - } - var fnPtr = fn(scope, locals, context) || noop; - // IE stupidity! - return fnPtr.apply - ? fnPtr.apply(context, args) - : fnPtr(args[0], args[1], args[2], args[3], args[4]); - }; - } - - // This is used with json array declaration - function arrayDeclaration () { - var elementFns = []; - if (peekToken().text != ']') { - do { - elementFns.push(expression()); - } while (expect(',')); - } - consume(']'); - return function(self, locals){ - var array = []; - for ( var i = 0; i < elementFns.length; i++) { - array.push(elementFns[i](self, locals)); - } - return array; - }; - } - - function object () { - var keyValues = []; - if (peekToken().text != '}') { - do { - var token = expect(), - key = token.string || token.text; - consume(":"); - var value = expression(); - keyValues.push({key:key, value:value}); - } while (expect(',')); - } - consume('}'); - return function(self, locals){ - var object = {}; - for ( var i = 0; i < keyValues.length; i++) { - var keyValue = keyValues[i]; - object[keyValue.key] = keyValue.value(self, locals); - } - return object; - }; - } -} - -////////////////////////////////////////////////// -// Parser helper functions -////////////////////////////////////////////////// - -function setter(obj, path, setValue) { - var element = path.split('.'); - for (var i = 0; element.length > 1; i++) { - var key = element.shift(); - var propertyObj = obj[key]; - if (!propertyObj) { - propertyObj = {}; - obj[key] = propertyObj; - } - obj = propertyObj; - } - obj[element.shift()] = setValue; - return setValue; -} - -/** - * Return the value accessible from the object by path. Any undefined traversals are ignored - * @param {Object} obj starting object - * @param {string} path path to traverse - * @param {boolean=true} bindFnToScope - * @returns value as accessible by path - */ -//TODO(misko): this function needs to be removed -function getter(obj, path, bindFnToScope) { - if (!path) return obj; - var keys = path.split('.'); - var key; - var lastInstance = obj; - var len = keys.length; - - for (var i = 0; i < len; i++) { - key = keys[i]; - if (obj) { - obj = (lastInstance = obj)[key]; - } - } - if (!bindFnToScope && isFunction(obj)) { - return bind(lastInstance, obj); - } - return obj; -} - -var getterFnCache = {}; - -/** - * Implementation of the "Black Hole" variant from: - * - http://jsperf.com/angularjs-parse-getter/4 - * - http://jsperf.com/path-evaluation-simplified/7 - */ -function cspSafeGetterFn(key0, key1, key2, key3, key4) { - return function(scope, locals) { - var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, - promise; - - if (pathVal === null || pathVal === undefined) return pathVal; - - pathVal = pathVal[key0]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key1 || pathVal === null || pathVal === undefined) return pathVal; - - pathVal = pathVal[key1]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key2 || pathVal === null || pathVal === undefined) return pathVal; - - pathVal = pathVal[key2]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key3 || pathVal === null || pathVal === undefined) return pathVal; - - pathVal = pathVal[key3]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - if (!key4 || pathVal === null || pathVal === undefined) return pathVal; - - pathVal = pathVal[key4]; - if (pathVal && pathVal.then) { - if (!("$$v" in pathVal)) { - promise = pathVal; - promise.$$v = undefined; - promise.then(function(val) { promise.$$v = val; }); - } - pathVal = pathVal.$$v; - } - return pathVal; - }; -} - -function getterFn(path, csp) { - if (getterFnCache.hasOwnProperty(path)) { - return getterFnCache[path]; - } - - var pathKeys = path.split('.'), - pathKeysLength = pathKeys.length, - fn; - - if (csp) { - fn = (pathKeysLength < 6) - ? cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4]) - : function(scope, locals) { - var i = 0, val; - do { - val = cspSafeGetterFn( - pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++] - )(scope, locals); - - locals = undefined; // clear after first iteration - scope = val; - } while (i < pathKeysLength); - return val; - } - } else { - var code = 'var l, fn, p;\n'; - forEach(pathKeys, function(key, index) { - code += 'if(s === null || s === undefined) return s;\n' + - 'l=s;\n' + - 's='+ (index - // we simply dereference 's' on any .dot notation - ? 's' - // but if we are first then we check locals first, and if so read it first - : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + - 'if (s && s.then) {\n' + - ' if (!("$$v" in s)) {\n' + - ' p=s;\n' + - ' p.$$v = undefined;\n' + - ' p.then(function(v) {p.$$v=v;});\n' + - '}\n' + - ' s=s.$$v\n' + - '}\n'; - }); - code += 'return s;'; - fn = Function('s', 'k', code); // s=scope, k=locals - fn.toString = function() { return code; }; - } - - return getterFnCache[path] = fn; -} - -/////////////////////////////////// - -/** - * @ngdoc function - * @name ng.$parse - * @function - * - * @description - * - * Converts Angular {@link guide/expression expression} into a function. - * - *
- *   var getter = $parse('user.name');
- *   var setter = getter.assign;
- *   var context = {user:{name:'angular'}};
- *   var locals = {user:{name:'local'}};
- *
- *   expect(getter(context)).toEqual('angular');
- *   setter(context, 'newValue');
- *   expect(context.user.name).toEqual('newValue');
- *   expect(getter(context, locals)).toEqual('local');
- * 
- * - * - * @param {string} expression String expression to compile. - * @returns {function(context, locals)} a function which represents the compiled expression: - * - * * `context` – `{object}` – an object against which any expressions embedded in the strings - * are evaluated against (tipically a scope object). - * * `locals` – `{object=}` – local variables context object, useful for overriding values in - * `context`. - * - * The return function also has an `assign` property, if the expression is assignable, which - * allows one to set values to expressions. - * - */ -function $ParseProvider() { - var cache = {}; - this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { - return function(exp) { - switch(typeof exp) { - case 'string': - return cache.hasOwnProperty(exp) - ? cache[exp] - : cache[exp] = parser(exp, false, $filter, $sniffer.csp); - case 'function': - return exp; - default: - return noop; - } - }; - }]; -} - -/** - * @ngdoc service - * @name ng.$q - * @requires $rootScope - * - * @description - * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). - * - * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an - * interface for interacting with an object that represents the result of an action that is - * performed asynchronously, and may or may not be finished at any given point in time. - * - * From the perspective of dealing with error handling, deferred and promise APIs are to - * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. - * - *
- *   // for the purpose of this example let's assume that variables `$q` and `scope` are
- *   // available in the current lexical scope (they could have been injected or passed in).
- *
- *   function asyncGreet(name) {
- *     var deferred = $q.defer();
- *
- *     setTimeout(function() {
- *       // since this fn executes async in a future turn of the event loop, we need to wrap
- *       // our code into an $apply call so that the model changes are properly observed.
- *       scope.$apply(function() {
- *         if (okToGreet(name)) {
- *           deferred.resolve('Hello, ' + name + '!');
- *         } else {
- *           deferred.reject('Greeting ' + name + ' is not allowed.');
- *         }
- *       });
- *     }, 1000);
- *
- *     return deferred.promise;
- *   }
- *
- *   var promise = asyncGreet('Robin Hood');
- *   promise.then(function(greeting) {
- *     alert('Success: ' + greeting);
- *   }, function(reason) {
- *     alert('Failed: ' + reason);
- *   });
- * 
- * - * At first it might not be obvious why this extra complexity is worth the trouble. The payoff - * comes in the way of - * [guarantees that promise and deferred APIs make](https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md). - * - * Additionally the promise api allows for composition that is very hard to do with the - * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. - * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the - * section on serial or parallel joining of promises. - * - * - * # The Deferred API - * - * A new instance of deferred is constructed by calling `$q.defer()`. - * - * The purpose of the deferred object is to expose the associated Promise instance as well as APIs - * that can be used for signaling the successful or unsuccessful completion of the task. - * - * **Methods** - * - * - `resolve(value)` – resolves the derived promise with the `value`. If the value is a rejection - * constructed via `$q.reject`, the promise will be rejected instead. - * - `reject(reason)` – rejects the derived promise with the `reason`. This is equivalent to - * resolving it with a rejection constructed via `$q.reject`. - * - * **Properties** - * - * - promise – `{Promise}` – promise object associated with this deferred. - * - * - * # The Promise API - * - * A new promise instance is created when a deferred instance is created and can be retrieved by - * calling `deferred.promise`. - * - * The purpose of the promise object is to allow for interested parties to get access to the result - * of the deferred task when it completes. - * - * **Methods** - * - * - `then(successCallback, errorCallback)` – regardless of when the promise was or will be resolved - * or rejected calls one of the success or error callbacks asynchronously as soon as the result - * is available. The callbacks are called with a single argument the result or rejection reason. - * - * This method *returns a new promise* which is resolved or rejected via the return value of the - * `successCallback` or `errorCallback`. - * - * - * # Chaining promises - * - * Because calling `then` api of a promise returns a new derived promise, it is easily possible - * to create a chain of promises: - * - *
- *   promiseB = promiseA.then(function(result) {
- *     return result + 1;
- *   });
- *
- *   // promiseB will be resolved immediately after promiseA is resolved and its value will be
- *   // the result of promiseA incremented by 1
- * 
- * - * It is possible to create chains of any length and since a promise can be resolved with another - * promise (which will defer its resolution further), it is possible to pause/defer resolution of - * the promises at any point in the chain. This makes it possible to implement powerful apis like - * $http's response interceptors. - * - * - * # Differences between Kris Kowal's Q and $q - * - * There are three main differences: - * - * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation - * mechanism in angular, which means faster propagation of resolution or rejection into your - * models and avoiding unnecessary browser repaints, which would result in flickering UI. - * - $q promises are recognized by the templating engine in angular, which means that in templates - * you can treat promises attached to a scope as if they were the resulting values. - * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains - * all the important functionality needed for common async tasks. - * - * # Testing - * - *
- *    it('should simulate promise', inject(function($q, $rootScope) {
- *      var deferred = $q.defer();
- *      var promise = deferred.promise;
- *      var resolvedValue;
- * 
- *      promise.then(function(value) { resolvedValue = value; });
- *      expect(resolvedValue).toBeUndefined();
- * 
- *      // Simulate resolving of promise
- *      deferred.resolve(123);
- *      // Note that the 'then' function does not get called synchronously.
- *      // This is because we want the promise API to always be async, whether or not
- *      // it got called synchronously or asynchronously.
- *      expect(resolvedValue).toBeUndefined();
- * 
- *      // Propagate promise resolution to 'then' functions using $apply().
- *      $rootScope.$apply();
- *      expect(resolvedValue).toEqual(123);
- *    });
- *  
- */ -function $QProvider() { - - this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { - return qFactory(function(callback) { - $rootScope.$evalAsync(callback); - }, $exceptionHandler); - }]; -} - - -/** - * Constructs a promise manager. - * - * @param {function(function)} nextTick Function for executing functions in the next turn. - * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for - * debugging purposes. - * @returns {object} Promise manager. - */ -function qFactory(nextTick, exceptionHandler) { - - /** - * @ngdoc - * @name ng.$q#defer - * @methodOf ng.$q - * @description - * Creates a `Deferred` object which represents a task which will finish in the future. - * - * @returns {Deferred} Returns a new instance of deferred. - */ - var defer = function() { - var pending = [], - value, deferred; - - deferred = { - - resolve: function(val) { - if (pending) { - var callbacks = pending; - pending = undefined; - value = ref(val); - - if (callbacks.length) { - nextTick(function() { - var callback; - for (var i = 0, ii = callbacks.length; i < ii; i++) { - callback = callbacks[i]; - value.then(callback[0], callback[1]); - } - }); - } - } - }, - - - reject: function(reason) { - deferred.resolve(reject(reason)); - }, - - - promise: { - then: function(callback, errback) { - var result = defer(); - - var wrappedCallback = function(value) { - try { - result.resolve((callback || defaultCallback)(value)); - } catch(e) { - exceptionHandler(e); - result.reject(e); - } - }; - - var wrappedErrback = function(reason) { - try { - result.resolve((errback || defaultErrback)(reason)); - } catch(e) { - exceptionHandler(e); - result.reject(e); - } - }; - - if (pending) { - pending.push([wrappedCallback, wrappedErrback]); - } else { - value.then(wrappedCallback, wrappedErrback); - } - - return result.promise; - } - } - }; - - return deferred; - }; - - - var ref = function(value) { - if (value && value.then) return value; - return { - then: function(callback) { - var result = defer(); - nextTick(function() { - result.resolve(callback(value)); - }); - return result.promise; - } - }; - }; - - - /** - * @ngdoc - * @name ng.$q#reject - * @methodOf ng.$q - * @description - * Creates a promise that is resolved as rejected with the specified `reason`. This api should be - * used to forward rejection in a chain of promises. If you are dealing with the last promise in - * a promise chain, you don't need to worry about it. - * - * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of - * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via - * a promise error callback and you want to forward the error to the promise derived from the - * current promise, you have to "rethrow" the error by returning a rejection constructed via - * `reject`. - * - *
-   *   promiseB = promiseA.then(function(result) {
-   *     // success: do something and resolve promiseB
-   *     //          with the old or a new result
-   *     return result;
-   *   }, function(reason) {
-   *     // error: handle the error if possible and
-   *     //        resolve promiseB with newPromiseOrValue,
-   *     //        otherwise forward the rejection to promiseB
-   *     if (canHandle(reason)) {
-   *      // handle the error and recover
-   *      return newPromiseOrValue;
-   *     }
-   *     return $q.reject(reason);
-   *   });
-   * 
- * - * @param {*} reason Constant, message, exception or an object representing the rejection reason. - * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. - */ - var reject = function(reason) { - return { - then: function(callback, errback) { - var result = defer(); - nextTick(function() { - result.resolve((errback || defaultErrback)(reason)); - }); - return result.promise; - } - }; - }; - - - /** - * @ngdoc - * @name ng.$q#when - * @methodOf ng.$q - * @description - * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. - * This is useful when you are dealing with an object that might or might not be a promise, or if - * the promise comes from a source that can't be trusted. - * - * @param {*} value Value or a promise - * @returns {Promise} Returns a promise of the passed value or promise - */ - var when = function(value, callback, errback) { - var result = defer(), - done; - - var wrappedCallback = function(value) { - try { - return (callback || defaultCallback)(value); - } catch (e) { - exceptionHandler(e); - return reject(e); - } - }; - - var wrappedErrback = function(reason) { - try { - return (errback || defaultErrback)(reason); - } catch (e) { - exceptionHandler(e); - return reject(e); - } - }; - - nextTick(function() { - ref(value).then(function(value) { - if (done) return; - done = true; - result.resolve(ref(value).then(wrappedCallback, wrappedErrback)); - }, function(reason) { - if (done) return; - done = true; - result.resolve(wrappedErrback(reason)); - }); - }); - - return result.promise; - }; - - - function defaultCallback(value) { - return value; - } - - - function defaultErrback(reason) { - return reject(reason); - } - - - /** - * @ngdoc - * @name ng.$q#all - * @methodOf ng.$q - * @description - * Combines multiple promises into a single promise that is resolved when all of the input - * promises are resolved. - * - * @param {Array.} promises An array of promises. - * @returns {Promise} Returns a single promise that will be resolved with an array of values, - * each value corresponding to the promise at the same index in the `promises` array. If any of - * the promises is resolved with a rejection, this resulting promise will be resolved with the - * same rejection. - */ - function all(promises) { - var deferred = defer(), - counter = promises.length, - results = []; - - if (counter) { - forEach(promises, function(promise, index) { - ref(promise).then(function(value) { - if (index in results) return; - results[index] = value; - if (!(--counter)) deferred.resolve(results); - }, function(reason) { - if (index in results) return; - deferred.reject(reason); - }); - }); - } else { - deferred.resolve(results); - } - - return deferred.promise; - } - - return { - defer: defer, - reject: reject, - when: when, - all: all - }; -} - -/** - * @ngdoc object - * @name ng.$routeProvider - * @function - * - * @description - * - * Used for configuring routes. See {@link ng.$route $route} for an example. - */ -function $RouteProvider(){ - var routes = {}; - - /** - * @ngdoc method - * @name ng.$routeProvider#when - * @methodOf ng.$routeProvider - * - * @param {string} path Route path (matched against `$location.path`). If `$location.path` - * contains redundant trailing slash or is missing one, the route will still match and the - * `$location.path` will be updated to add or drop the trailing slash to exactly match the - * route definition. - * - * `path` can contain named groups starting with a colon (`:name`). All characters up to the - * next slash are matched and stored in `$routeParams` under the given `name` when the route - * matches. - * - * @param {Object} route Mapping information to be assigned to `$route.current` on route - * match. - * - * Object properties: - * - * - `controller` – `{(string|function()=}` – Controller fn that should be associated with newly - * created scope or the name of a {@link angular.Module#controller registered controller} - * if passed as a string. - * - `template` – `{string=}` – html template as a string that should be used by - * {@link ng.directive:ngView ngView} or - * {@link ng.directive:ngInclude ngInclude} directives. - * this property takes precedence over `templateUrl`. - * - `templateUrl` – `{string=}` – path to an html template that should be used by - * {@link ng.directive:ngView ngView}. - * - `resolve` - `{Object.=}` - An optional map of dependencies which should - * be injected into the controller. If any of these dependencies are promises, they will be - * resolved and converted to a value before the controller is instantiated and the - * `$routeChangeSuccess` event is fired. The map object is: - * - * - `key` – `{string}`: a name of a dependency to be injected into the controller. - * - `factory` - `{string|function}`: If `string` then it is an alias for a service. - * Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} - * and the return value is treated as the dependency. If the result is a promise, it is resolved - * before its value is injected into the controller. - * - * - `redirectTo` – {(string|function())=} – value to update - * {@link ng.$location $location} path with and trigger route redirection. - * - * If `redirectTo` is a function, it will be called with the following parameters: - * - * - `{Object.}` - route parameters extracted from the current - * `$location.path()` by applying the current route templateUrl. - * - `{string}` - current `$location.path()` - * - `{Object}` - current `$location.search()` - * - * The custom `redirectTo` function is expected to return a string which will be used - * to update `$location.path()` and `$location.search()`. - * - * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search() - * changes. - * - * If the option is set to `false` and url in the browser changes, then - * `$routeUpdate` event is broadcasted on the root scope. - * - * @returns {Object} self - * - * @description - * Adds a new route definition to the `$route` service. - */ - this.when = function(path, route) { - routes[path] = extend({reloadOnSearch: true}, route); - - // create redirection for trailing slashes - if (path) { - var redirectPath = (path[path.length-1] == '/') - ? path.substr(0, path.length-1) - : path +'/'; - - routes[redirectPath] = {redirectTo: path}; - } - - return this; - }; - - /** - * @ngdoc method - * @name ng.$routeProvider#otherwise - * @methodOf ng.$routeProvider - * - * @description - * Sets route definition that will be used on route change when no other route definition - * is matched. - * - * @param {Object} params Mapping information to be assigned to `$route.current`. - * @returns {Object} self - */ - this.otherwise = function(params) { - this.when(null, params); - return this; - }; - - - this.$get = ['$rootScope', '$location', '$routeParams', '$q', '$injector', '$http', '$templateCache', - function( $rootScope, $location, $routeParams, $q, $injector, $http, $templateCache) { - - /** - * @ngdoc object - * @name ng.$route - * @requires $location - * @requires $routeParams - * - * @property {Object} current Reference to the current route definition. - * The route definition contains: - * - * - `controller`: The controller constructor as define in route definition. - * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for - * controller instantiation. The `locals` contain - * the resolved values of the `resolve` map. Additionally the `locals` also contain: - * - * - `$scope` - The current route scope. - * - `$template` - The current route template HTML. - * - * @property {Array.} routes Array of all configured routes. - * - * @description - * Is used for deep-linking URLs to controllers and views (HTML partials). - * It watches `$location.url()` and tries to map the path to an existing route definition. - * - * You can define routes through {@link ng.$routeProvider $routeProvider}'s API. - * - * The `$route` service is typically used in conjunction with {@link ng.directive:ngView ngView} - * directive and the {@link ng.$routeParams $routeParams} service. - * - * @example - This example shows how changing the URL hash causes the `$route` to match a route against the - URL, and the `ngView` pulls in the partial. - - Note that this example is using {@link ng.directive:script inlined templates} - to get it working on jsfiddle as well. - - - -
- Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
- -
-
- -
$location.path() = {{$location.path()}}
-
$route.current.templateUrl = {{$route.current.templateUrl}}
-
$route.current.params = {{$route.current.params}}
-
$route.current.scope.name = {{$route.current.scope.name}}
-
$routeParams = {{$routeParams}}
-
-
- - - controller: {{name}}
- Book Id: {{params.bookId}}
-
- - - controller: {{name}}
- Book Id: {{params.bookId}}
- Chapter Id: {{params.chapterId}} -
- - - angular.module('ngView', [], function($routeProvider, $locationProvider) { - $routeProvider.when('/Book/:bookId', { - templateUrl: 'book.html', - controller: BookCntl, - resolve: { - // I will cause a 1 second delay - delay: function($q, $timeout) { - var delay = $q.defer(); - $timeout(delay.resolve, 1000); - return delay.promise; - } - } - }); - $routeProvider.when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: ChapterCntl - }); - - // configure html5 to get links working on jsfiddle - $locationProvider.html5Mode(true); - }); - - function MainCntl($scope, $route, $routeParams, $location) { - $scope.$route = $route; - $scope.$location = $location; - $scope.$routeParams = $routeParams; - } - - function BookCntl($scope, $routeParams) { - $scope.name = "BookCntl"; - $scope.params = $routeParams; - } - - function ChapterCntl($scope, $routeParams) { - $scope.name = "ChapterCntl"; - $scope.params = $routeParams; - } - - - - it('should load and compile correct template', function() { - element('a:contains("Moby: Ch1")').click(); - var content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: ChapterCntl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element('a:contains("Scarlet")').click(); - sleep(2); // promises are not part of scenario waiting - content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: BookCntl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
- */ - - /** - * @ngdoc event - * @name ng.$route#$routeChangeStart - * @eventOf ng.$route - * @eventType broadcast on root scope - * @description - * Broadcasted before a route change. At this point the route services starts - * resolving all of the dependencies needed for the route change to occurs. - * Typically this involves fetching the view template as well as any dependencies - * defined in `resolve` route property. Once all of the dependencies are resolved - * `$routeChangeSuccess` is fired. - * - * @param {Route} next Future route information. - * @param {Route} current Current route information. - */ - - /** - * @ngdoc event - * @name ng.$route#$routeChangeSuccess - * @eventOf ng.$route - * @eventType broadcast on root scope - * @description - * Broadcasted after a route dependencies are resolved. - * {@link ng.directive:ngView ngView} listens for the directive - * to instantiate the controller and render the view. - * - * @param {Object} angularEvent Synthetic event object. - * @param {Route} current Current route information. - * @param {Route|Undefined} previous Previous route information, or undefined if current is first route entered. - */ - - /** - * @ngdoc event - * @name ng.$route#$routeChangeError - * @eventOf ng.$route - * @eventType broadcast on root scope - * @description - * Broadcasted if any of the resolve promises are rejected. - * - * @param {Route} current Current route information. - * @param {Route} previous Previous route information. - * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. - */ - - /** - * @ngdoc event - * @name ng.$route#$routeUpdate - * @eventOf ng.$route - * @eventType broadcast on root scope - * @description - * - * The `reloadOnSearch` property has been set to false, and we are reusing the same - * instance of the Controller. - */ - - var forceReload = false, - $route = { - routes: routes, - - /** - * @ngdoc method - * @name ng.$route#reload - * @methodOf ng.$route - * - * @description - * Causes `$route` service to reload the current route even if - * {@link ng.$location $location} hasn't changed. - * - * As a result of that, {@link ng.directive:ngView ngView} - * creates new scope, reinstantiates the controller. - */ - reload: function() { - forceReload = true; - $rootScope.$evalAsync(updateRoute); - } - }; - - $rootScope.$on('$locationChangeSuccess', updateRoute); - - return $route; - - ///////////////////////////////////////////////////// - - /** - * @param on {string} current url - * @param when {string} route when template to match the url against - * @return {?Object} - */ - function switchRouteMatcher(on, when) { - // TODO(i): this code is convoluted and inefficient, we should construct the route matching - // regex only once and then reuse it - - // Escape regexp special characters. - when = '^' + when.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&") + '$'; - var regex = '', - params = [], - dst = {}; - - var re = /:(\w+)/g, - paramMatch, - lastMatchedIndex = 0; - - while ((paramMatch = re.exec(when)) !== null) { - // Find each :param in `when` and replace it with a capturing group. - // Append all other sections of when unchanged. - regex += when.slice(lastMatchedIndex, paramMatch.index); - regex += '([^\\/]*)'; - params.push(paramMatch[1]); - lastMatchedIndex = re.lastIndex; - } - // Append trailing path part. - regex += when.substr(lastMatchedIndex); - - var match = on.match(new RegExp(regex)); - if (match) { - forEach(params, function(name, index) { - dst[name] = match[index + 1]; - }); - } - return match ? dst : null; - } - - function updateRoute() { - var next = parseRoute(), - last = $route.current; - - if (next && last && next.$$route === last.$$route - && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) { - last.params = next.params; - copy(last.params, $routeParams); - $rootScope.$broadcast('$routeUpdate', last); - } else if (next || last) { - forceReload = false; - $rootScope.$broadcast('$routeChangeStart', next, last); - $route.current = next; - if (next) { - if (next.redirectTo) { - if (isString(next.redirectTo)) { - $location.path(interpolate(next.redirectTo, next.params)).search(next.params) - .replace(); - } else { - $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) - .replace(); - } - } - } - - $q.when(next). - then(function() { - if (next) { - var keys = [], - values = [], - template; - - forEach(next.resolve || {}, function(value, key) { - keys.push(key); - values.push(isString(value) ? $injector.get(value) : $injector.invoke(value)); - }); - if (isDefined(template = next.template)) { - } else if (isDefined(template = next.templateUrl)) { - template = $http.get(template, {cache: $templateCache}). - then(function(response) { return response.data; }); - } - if (isDefined(template)) { - keys.push('$template'); - values.push(template); - } - return $q.all(values).then(function(values) { - var locals = {}; - forEach(values, function(value, index) { - locals[keys[index]] = value; - }); - return locals; - }); - } - }). - // after route change - then(function(locals) { - if (next == $route.current) { - if (next) { - next.locals = locals; - copy(next.params, $routeParams); - } - $rootScope.$broadcast('$routeChangeSuccess', next, last); - } - }, function(error) { - if (next == $route.current) { - $rootScope.$broadcast('$routeChangeError', next, last, error); - } - }); - } - } - - - /** - * @returns the current active route, by matching it against the URL - */ - function parseRoute() { - // Match a route - var params, match; - forEach(routes, function(route, path) { - if (!match && (params = switchRouteMatcher($location.path(), path))) { - match = inherit(route, { - params: extend({}, $location.search(), params), - pathParams: params}); - match.$$route = route; - } - }); - // No route matched; fallback to "otherwise" route - return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); - } - - /** - * @returns interpolation of the redirect path with the parametrs - */ - function interpolate(string, params) { - var result = []; - forEach((string||'').split(':'), function(segment, i) { - if (i == 0) { - result.push(segment); - } else { - var segmentMatch = segment.match(/(\w+)(.*)/); - var key = segmentMatch[1]; - result.push(params[key]); - result.push(segmentMatch[2] || ''); - delete params[key]; - } - }); - return result.join(''); - } - }]; -} - -/** - * @ngdoc object - * @name ng.$routeParams - * @requires $route - * - * @description - * Current set of route parameters. The route parameters are a combination of the - * {@link ng.$location $location} `search()`, and `path()`. The `path` parameters - * are extracted when the {@link ng.$route $route} path is matched. - * - * In case of parameter name collision, `path` params take precedence over `search` params. - * - * The service guarantees that the identity of the `$routeParams` object will remain unchanged - * (but its properties will likely change) even when a route change occurs. - * - * @example - *
- *  // Given:
- *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
- *  // Route: /Chapter/:chapterId/Section/:sectionId
- *  //
- *  // Then
- *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
- * 
- */ -function $RouteParamsProvider() { - this.$get = valueFn({}); -} - -/** - * DESIGN NOTES - * - * The design decisions behind the scope are heavily favored for speed and memory consumption. - * - * The typical use of scope is to watch the expressions, which most of the time return the same - * value as last time so we optimize the operation. - * - * Closures construction is expensive in terms of speed as well as memory: - * - No closures, instead use prototypical inheritance for API - * - Internal state needs to be stored on scope directly, which means that private state is - * exposed as $$____ properties - * - * Loop operations are optimized by using while(count--) { ... } - * - this means that in order to keep the same order of execution as addition we have to add - * items to the array at the beginning (shift) instead of at the end (push) - * - * Child scopes are created and removed often - * - Using an array would be slow since inserts in middle are expensive so we use linked list - * - * There are few watches then a lot of observers. This is why you don't want the observer to be - * implemented in the same way as watch. Watch requires return of initialization function which - * are expensive to construct. - */ - - -/** - * @ngdoc object - * @name ng.$rootScopeProvider - * @description - * - * Provider for the $rootScope service. - */ - -/** - * @ngdoc function - * @name ng.$rootScopeProvider#digestTtl - * @methodOf ng.$rootScopeProvider - * @description - * - * Sets the number of digest iterations the scope should attempt to execute before giving up and - * assuming that the model is unstable. - * - * The current default is 10 iterations. - * - * @param {number} limit The number of digest iterations. - */ - - -/** - * @ngdoc object - * @name ng.$rootScope - * @description - * - * Every application has a single root {@link ng.$rootScope.Scope scope}. - * All other scopes are child scopes of the root scope. Scopes provide mechanism for watching the model and provide - * event processing life-cycle. See {@link guide/scope developer guide on scopes}. - */ -function $RootScopeProvider(){ - var TTL = 10; - - this.digestTtl = function(value) { - if (arguments.length) { - TTL = value; - } - return TTL; - }; - - this.$get = ['$injector', '$exceptionHandler', '$parse', - function( $injector, $exceptionHandler, $parse) { - - /** - * @ngdoc function - * @name ng.$rootScope.Scope - * - * @description - * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the - * {@link AUTO.$injector $injector}. Child scopes are created using the - * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when - * compiled HTML template is executed.) - * - * Here is a simple scope snippet to show how you can interact with the scope. - *
-        angular.injector(['ng']).invoke(function($rootScope) {
-           var scope = $rootScope.$new();
-           scope.salutation = 'Hello';
-           scope.name = 'World';
-
-           expect(scope.greeting).toEqual(undefined);
-
-           scope.$watch('name', function() {
-             scope.greeting = scope.salutation + ' ' + scope.name + '!';
-           }); // initialize the watch
-
-           expect(scope.greeting).toEqual(undefined);
-           scope.name = 'Misko';
-           // still old value, since watches have not been called yet
-           expect(scope.greeting).toEqual(undefined);
-
-           scope.$digest(); // fire all  the watches
-           expect(scope.greeting).toEqual('Hello Misko!');
-        });
-     * 
- * - * # Inheritance - * A scope can inherit from a parent scope, as in this example: - *
-         var parent = $rootScope;
-         var child = parent.$new();
-
-         parent.salutation = "Hello";
-         child.name = "World";
-         expect(child.salutation).toEqual('Hello');
-
-         child.salutation = "Welcome";
-         expect(child.salutation).toEqual('Welcome');
-         expect(parent.salutation).toEqual('Hello');
-     * 
- * - * - * @param {Object.=} providers Map of service factory which need to be provided - * for the current scope. Defaults to {@link ng}. - * @param {Object.=} instanceCache Provides pre-instantiated services which should - * append/override services provided by `providers`. This is handy when unit-testing and having - * the need to override a default service. - * @returns {Object} Newly created scope. - * - */ - function Scope() { - this.$id = nextUid(); - this.$$phase = this.$parent = this.$$watchers = - this.$$nextSibling = this.$$prevSibling = - this.$$childHead = this.$$childTail = null; - this['this'] = this.$root = this; - this.$$destroyed = false; - this.$$asyncQueue = []; - this.$$listeners = {}; - this.$$isolateBindings = {}; - } - - /** - * @ngdoc property - * @name ng.$rootScope.Scope#$id - * @propertyOf ng.$rootScope.Scope - * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for - * debugging. - */ - - - Scope.prototype = { - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$new - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Creates a new child {@link ng.$rootScope.Scope scope}. - * - * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and - * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the scope - * hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. - * - * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is desired for - * the scope and its child scopes to be permanently detached from the parent and thus stop - * participating in model change detection and listener notification by invoking. - * - * @param {boolean} isolate if true then the scope does not prototypically inherit from the - * parent scope. The scope is isolated, as it can not see parent scope properties. - * When creating widgets it is useful for the widget to not accidentally read parent - * state. - * - * @returns {Object} The newly created child scope. - * - */ - $new: function(isolate) { - var Child, - child; - - if (isFunction(isolate)) { - // TODO: remove at some point - throw Error('API-CHANGE: Use $controller to instantiate controllers.'); - } - if (isolate) { - child = new Scope(); - child.$root = this.$root; - } else { - Child = function() {}; // should be anonymous; This is so that when the minifier munges - // the name it does not become random set of chars. These will then show up as class - // name in the debugger. - Child.prototype = this; - child = new Child(); - child.$id = nextUid(); - } - child['this'] = child; - child.$$listeners = {}; - child.$parent = this; - child.$$asyncQueue = []; - child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; - child.$$prevSibling = this.$$childTail; - if (this.$$childHead) { - this.$$childTail.$$nextSibling = child; - this.$$childTail = child; - } else { - this.$$childHead = this.$$childTail = child; - } - return child; - }, - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$watch - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Registers a `listener` callback to be executed whenever the `watchExpression` changes. - * - * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest $digest()} and - * should return the value which will be watched. (Since {@link ng.$rootScope.Scope#$digest $digest()} - * reruns when it detects changes the `watchExpression` can execute multiple times per - * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) - * - The `listener` is called only when the value from the current `watchExpression` and the - * previous call to `watchExpression` are not equal (with the exception of the initial run, - * see below). The inequality is determined according to - * {@link angular.equals} function. To save the value of the object for later comparison, the - * {@link angular.copy} function is used. It also means that watching complex options will - * have adverse memory and performance implications. - * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This - * is achieved by rerunning the watchers until no changes are detected. The rerun iteration - * limit is 10 to prevent an infinite loop deadlock. - * - * - * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, - * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` - * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a change is - * detected, be prepared for multiple calls to your listener.) - * - * After a watcher is registered with the scope, the `listener` fn is called asynchronously - * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the - * watcher. In rare cases, this is undesirable because the listener is called when the result - * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you - * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the - * listener was called due to initialization. - * - * - * # Example - *
-           // let's assume that scope was dependency injected as the $rootScope
-           var scope = $rootScope;
-           scope.name = 'misko';
-           scope.counter = 0;
-
-           expect(scope.counter).toEqual(0);
-           scope.$watch('name', function(newValue, oldValue) { scope.counter = scope.counter + 1; });
-           expect(scope.counter).toEqual(0);
-
-           scope.$digest();
-           // no variable change
-           expect(scope.counter).toEqual(0);
-
-           scope.name = 'adam';
-           scope.$digest();
-           expect(scope.counter).toEqual(1);
-       * 
- * - * - * - * @param {(function()|string)} watchExpression Expression that is evaluated on each - * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers a - * call to the `listener`. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(scope)`: called with current `scope` as a parameter. - * @param {(function()|string)=} listener Callback called whenever the return value of - * the `watchExpression` changes. - * - * - `string`: Evaluated as {@link guide/expression expression} - * - `function(newValue, oldValue, scope)`: called with current and previous values as parameters. - * - * @param {boolean=} objectEquality Compare object for equality rather than for reference. - * @returns {function()} Returns a deregistration function for this listener. - */ - $watch: function(watchExp, listener, objectEquality) { - var scope = this, - get = compileToFn(watchExp, 'watch'), - array = scope.$$watchers, - watcher = { - fn: listener, - last: initWatchVal, - get: get, - exp: watchExp, - eq: !!objectEquality - }; - - // in the case user pass string, we need to compile it, do we really need this ? - if (!isFunction(listener)) { - var listenFn = compileToFn(listener || noop, 'listener'); - watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; - } - - if (!array) { - array = scope.$$watchers = []; - } - // we use unshift since we use a while loop in $digest for speed. - // the while loop reads in reverse order. - array.unshift(watcher); - - return function() { - arrayRemove(array, watcher); - }; - }, - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$digest - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and its children. - * Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change the model, the - * `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} until no more listeners are - * firing. This means that it is possible to get into an infinite loop. This function will throw - * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 10. - * - * Usually you don't call `$digest()` directly in - * {@link ng.directive:ngController controllers} or in - * {@link ng.$compileProvider#directive directives}. - * Instead a call to {@link ng.$rootScope.Scope#$apply $apply()} (typically from within a - * {@link ng.$compileProvider#directive directives}) will force a `$digest()`. - * - * If you want to be notified whenever `$digest()` is called, - * you can register a `watchExpression` function with {@link ng.$rootScope.Scope#$watch $watch()} - * with no `listener`. - * - * You may have a need to call `$digest()` from within unit-tests, to simulate the scope - * life-cycle. - * - * # Example - *
-           var scope = ...;
-           scope.name = 'misko';
-           scope.counter = 0;
-
-           expect(scope.counter).toEqual(0);
-           scope.$watch('name', function(newValue, oldValue) {
-             scope.counter = scope.counter + 1;
-           });
-           expect(scope.counter).toEqual(0);
-
-           scope.$digest();
-           // no variable change
-           expect(scope.counter).toEqual(0);
-
-           scope.name = 'adam';
-           scope.$digest();
-           expect(scope.counter).toEqual(1);
-       * 
- * - */ - $digest: function() { - var watch, value, last, - watchers, - asyncQueue, - length, - dirty, ttl = TTL, - next, current, target = this, - watchLog = [], - logIdx, logMsg; - - beginPhase('$digest'); - - do { - dirty = false; - current = target; - do { - asyncQueue = current.$$asyncQueue; - while(asyncQueue.length) { - try { - current.$eval(asyncQueue.shift()); - } catch (e) { - $exceptionHandler(e); - } - } - if ((watchers = current.$$watchers)) { - // process our watches - length = watchers.length; - while (length--) { - try { - watch = watchers[length]; - // Most common watches are on primitives, in which case we can short - // circuit it with === operator, only when === fails do we use .equals - if ((value = watch.get(current)) !== (last = watch.last) && - !(watch.eq - ? equals(value, last) - : (typeof value == 'number' && typeof last == 'number' - && isNaN(value) && isNaN(last)))) { - dirty = true; - watch.last = watch.eq ? copy(value) : value; - watch.fn(value, ((last === initWatchVal) ? value : last), current); - if (ttl < 5) { - logIdx = 4 - ttl; - if (!watchLog[logIdx]) watchLog[logIdx] = []; - logMsg = (isFunction(watch.exp)) - ? 'fn: ' + (watch.exp.name || watch.exp.toString()) - : watch.exp; - logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); - watchLog[logIdx].push(logMsg); - } - } - } catch (e) { - $exceptionHandler(e); - } - } - } - - // Insanity Warning: scope depth-first traversal - // yes, this code is a bit crazy, but it works and we have tests to prove it! - // this piece should be kept in sync with the traversal in $broadcast - if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) { - while(current !== target && !(next = current.$$nextSibling)) { - current = current.$parent; - } - } - } while ((current = next)); - - if(dirty && !(ttl--)) { - clearPhase(); - throw Error(TTL + ' $digest() iterations reached. Aborting!\n' + - 'Watchers fired in the last 5 iterations: ' + toJson(watchLog)); - } - } while (dirty || asyncQueue.length); - - clearPhase(); - }, - - - /** - * @ngdoc event - * @name ng.$rootScope.Scope#$destroy - * @eventOf ng.$rootScope.Scope - * @eventType broadcast on scope being destroyed - * - * @description - * Broadcasted when a scope and its children are being destroyed. - */ - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$destroy - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Removes the current scope (and all of its children) from the parent scope. Removal implies - * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer - * propagate to the current scope and its children. Removal also implies that the current - * scope is eligible for garbage collection. - * - * The `$destroy()` is usually used by directives such as - * {@link ng.directive:ngRepeat ngRepeat} for managing the - * unrolling of the loop. - * - * Just before a scope is destroyed a `$destroy` event is broadcasted on this scope. - * Application code can register a `$destroy` event handler that will give it chance to - * perform any necessary cleanup. - */ - $destroy: function() { - // we can't destroy the root scope or a scope that has been already destroyed - if ($rootScope == this || this.$$destroyed) return; - var parent = this.$parent; - - this.$broadcast('$destroy'); - this.$$destroyed = true; - - if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; - if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; - if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; - if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; - - // This is bogus code that works around Chrome's GC leak - // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 - this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = - this.$$childTail = null; - }, - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$eval - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Executes the `expression` on the current scope returning the result. Any exceptions in the - * expression are propagated (uncaught). This is useful when evaluating Angular expressions. - * - * # Example - *
-           var scope = ng.$rootScope.Scope();
-           scope.a = 1;
-           scope.b = 2;
-
-           expect(scope.$eval('a+b')).toEqual(3);
-           expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3);
-       * 
- * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $eval: function(expr, locals) { - return $parse(expr)(this, locals); - }, - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$evalAsync - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Executes the expression on the current scope at a later point in time. - * - * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that: - * - * - it will execute in the current script execution context (before any DOM rendering). - * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after - * `expression` execution. - * - * Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * @param {(string|function())=} expression An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with the current `scope` parameter. - * - */ - $evalAsync: function(expr) { - this.$$asyncQueue.push(expr); - }, - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$apply - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * `$apply()` is used to execute an expression in angular from outside of the angular framework. - * (For example from browser DOM events, setTimeout, XHR or third party libraries). - * Because we are calling into the angular framework we need to perform proper scope life-cycle - * of {@link ng.$exceptionHandler exception handling}, - * {@link ng.$rootScope.Scope#$digest executing watches}. - * - * ## Life cycle - * - * # Pseudo-Code of `$apply()` - *
-           function $apply(expr) {
-             try {
-               return $eval(expr);
-             } catch (e) {
-               $exceptionHandler(e);
-             } finally {
-               $root.$digest();
-             }
-           }
-       * 
- * - * - * Scope's `$apply()` method transitions through the following stages: - * - * 1. The {@link guide/expression expression} is executed using the - * {@link ng.$rootScope.Scope#$eval $eval()} method. - * 2. Any exceptions from the execution of the expression are forwarded to the - * {@link ng.$exceptionHandler $exceptionHandler} service. - * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the expression - * was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. - * - * - * @param {(string|function())=} exp An angular expression to be executed. - * - * - `string`: execute using the rules as defined in {@link guide/expression expression}. - * - `function(scope)`: execute the function with current `scope` parameter. - * - * @returns {*} The result of evaluating the expression. - */ - $apply: function(expr) { - try { - beginPhase('$apply'); - return this.$eval(expr); - } catch (e) { - $exceptionHandler(e); - } finally { - clearPhase(); - try { - $rootScope.$digest(); - } catch (e) { - $exceptionHandler(e); - throw e; - } - } - }, - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$on - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for discussion of - * event life cycle. - * - * The event listener function format is: `function(event, args...)`. The `event` object - * passed into the listener has the following attributes: - * - * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or `$broadcast`-ed. - * - `currentScope` - `{Scope}`: the current scope which is handling the event. - * - `name` - `{string}`: Name of the event. - * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel further event - * propagation (available only for events that were `$emit`-ed). - * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag to true. - * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. - * - * @param {string} name Event name to listen on. - * @param {function(event, args...)} listener Function to call when the event is emitted. - * @returns {function()} Returns a deregistration function for this listener. - */ - $on: function(name, listener) { - var namedListeners = this.$$listeners[name]; - if (!namedListeners) { - this.$$listeners[name] = namedListeners = []; - } - namedListeners.push(listener); - - return function() { - namedListeners[indexOf(namedListeners, listener)] = null; - }; - }, - - - /** - * @ngdoc function - * @name ng.$rootScope.Scope#$emit - * @methodOf ng.$rootScope.Scope - * @function - * - * @description - * Dispatches an event `name` upwards through the scope hierarchy notifying the - * registered {@link ng.$rootScope.Scope#$on} listeners. - * - * The event life cycle starts at the scope on which `$emit` was called. All - * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get notified. - * Afterwards, the event traverses upwards toward the root scope and calls all registered - * listeners along the way. The event will stop propagating if one of the listeners cancels it. - * - * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed - * onto the {@link ng.$exceptionHandler $exceptionHandler} service. - * - * @param {string} name Event name to emit. - * @param {...*} args Optional set of arguments which will be passed onto the event listeners. - * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} - */ - $emit: function(name, args) { - var empty = [], - namedListeners, - scope = this, - stopPropagation = false, - event = { - name: name, - targetScope: scope, - stopPropagation: function() {stopPropagation = true;}, - preventDefault: function() { - event.defaultPrevented = true; - }, - defaultPrevented: false - }, - listenerArgs = concat([event], arguments, 1), - i, length; - - do { - namedListeners = scope.$$listeners[name] || empty; - event.currentScope = scope; - for (i=0, length=namedListeners.length; i 7), - hasEvent: function(event) { - // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have - // it. In particular the event is not fired when backspace or delete key are pressed or - // when cut operation is performed. - if (event == 'input' && msie == 9) return false; - - if (isUndefined(eventSupport[event])) { - var divElm = $window.document.createElement('div'); - eventSupport[event] = 'on' + event in divElm; - } - - return eventSupport[event]; - }, - // TODO(i): currently there is no way to feature detect CSP without triggering alerts - csp: false - }; - }]; -} - -/** - * @ngdoc object - * @name ng.$window - * - * @description - * A reference to the browser's `window` object. While `window` - * is globally available in JavaScript, it causes testability problems, because - * it is a global variable. In angular we always refer to it through the - * `$window` service, so it may be overriden, removed or mocked for testing. - * - * All expressions are evaluated with respect to current scope so they don't - * suffer from window globality. - * - * @example - - - -
- - -
-
- - it('should display the greeting in the input box', function() { - input('greeting').enter('Hello, E2E Tests'); - // If we click the button it will block the test runner - // element(':button').click(); - }); - -
- */ -function $WindowProvider(){ - this.$get = valueFn(window); -} - -/** - * Parse headers into key value object - * - * @param {string} headers Raw headers as a string - * @returns {Object} Parsed headers as key value object - */ -function parseHeaders(headers) { - var parsed = {}, key, val, i; - - if (!headers) return parsed; - - forEach(headers.split('\n'), function(line) { - i = line.indexOf(':'); - key = lowercase(trim(line.substr(0, i))); - val = trim(line.substr(i + 1)); - - if (key) { - if (parsed[key]) { - parsed[key] += ', ' + val; - } else { - parsed[key] = val; - } - } - }); - - return parsed; -} - - -/** - * Returns a function that provides access to parsed headers. - * - * Headers are lazy parsed when first requested. - * @see parseHeaders - * - * @param {(string|Object)} headers Headers to provide access to. - * @returns {function(string=)} Returns a getter function which if called with: - * - * - if called with single an argument returns a single header value or null - * - if called with no arguments returns an object containing all headers. - */ -function headersGetter(headers) { - var headersObj = isObject(headers) ? headers : undefined; - - return function(name) { - if (!headersObj) headersObj = parseHeaders(headers); - - if (name) { - return headersObj[lowercase(name)] || null; - } - - return headersObj; - }; -} - - -/** - * Chain all given functions - * - * This function is used for both request and response transforming - * - * @param {*} data Data to transform. - * @param {function(string=)} headers Http headers getter fn. - * @param {(function|Array.)} fns Function or an array of functions. - * @returns {*} Transformed data. - */ -function transformData(data, headers, fns) { - if (isFunction(fns)) - return fns(data, headers); - - forEach(fns, function(fn) { - data = fn(data, headers); - }); - - return data; -} - - -function isSuccess(status) { - return 200 <= status && status < 300; -} - - -function $HttpProvider() { - var JSON_START = /^\s*(\[|\{[^\{])/, - JSON_END = /[\}\]]\s*$/, - PROTECTION_PREFIX = /^\)\]\}',?\n/; - - var $config = this.defaults = { - // transform incoming response data - transformResponse: [function(data) { - if (isString(data)) { - // strip json vulnerability protection prefix - data = data.replace(PROTECTION_PREFIX, ''); - if (JSON_START.test(data) && JSON_END.test(data)) - data = fromJson(data, true); - } - return data; - }], - - // transform outgoing request data - transformRequest: [function(d) { - return isObject(d) && !isFile(d) ? toJson(d) : d; - }], - - // default headers - headers: { - common: { - 'Accept': 'application/json, text/plain, */*', - 'X-Requested-With': 'XMLHttpRequest' - }, - post: {'Content-Type': 'application/json;charset=utf-8'}, - put: {'Content-Type': 'application/json;charset=utf-8'} - } - }; - - var providerResponseInterceptors = this.responseInterceptors = []; - - this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', - function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { - - var defaultCache = $cacheFactory('$http'), - responseInterceptors = []; - - forEach(providerResponseInterceptors, function(interceptor) { - responseInterceptors.push( - isString(interceptor) - ? $injector.get(interceptor) - : $injector.invoke(interceptor) - ); - }); - - - /** - * @ngdoc function - * @name ng.$http - * @requires $httpBackend - * @requires $browser - * @requires $cacheFactory - * @requires $rootScope - * @requires $q - * @requires $injector - * - * @description - * The `$http` service is a core Angular service that facilitates communication with the remote - * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest - * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. - * - * For unit testing applications that use `$http` service, see - * {@link ngMock.$httpBackend $httpBackend mock}. - * - * For a higher level of abstraction, please check out the {@link ngResource.$resource - * $resource} service. - * - * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by - * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage - * it is important to familiarize yourself with these APIs and the guarantees they provide. - * - * - * # General usage - * The `$http` service is a function which takes a single argument — a configuration object — - * that is used to generate an HTTP request and returns a {@link ng.$q promise} - * with two $http specific methods: `success` and `error`. - * - *
-     *   $http({method: 'GET', url: '/someUrl'}).
-     *     success(function(data, status, headers, config) {
-     *       // this callback will be called asynchronously
-     *       // when the response is available
-     *     }).
-     *     error(function(data, status, headers, config) {
-     *       // called asynchronously if an error occurs
-     *       // or server returns response with an error status.
-     *     });
-     * 
- * - * Since the returned value of calling the $http function is a `promise`, you can also use - * the `then` method to register callbacks, and these callbacks will receive a single argument – - * an object representing the response. See the API signature and type info below for more - * details. - * - * A response status code between 200 and 299 is considered a success status and - * will result in the success callback being called. Note that if the response is a redirect, - * XMLHttpRequest will transparently follow it, meaning that the error callback will not be - * called for such responses. - * - * # Shortcut methods - * - * Since all invocations of the $http service require passing in an HTTP method and URL, and - * POST/PUT requests require request data to be provided as well, shortcut methods - * were created: - * - *
-     *   $http.get('/someUrl').success(successCallback);
-     *   $http.post('/someUrl', data).success(successCallback);
-     * 
- * - * Complete list of shortcut methods: - * - * - {@link ng.$http#get $http.get} - * - {@link ng.$http#head $http.head} - * - {@link ng.$http#post $http.post} - * - {@link ng.$http#put $http.put} - * - {@link ng.$http#delete $http.delete} - * - {@link ng.$http#jsonp $http.jsonp} - * - * - * # Setting HTTP Headers - * - * The $http service will automatically add certain HTTP headers to all requests. These defaults - * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration - * object, which currently contains this default configuration: - * - * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): - * - `Accept: application/json, text/plain, * / *` - * - `X-Requested-With: XMLHttpRequest` - * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) - * - `Content-Type: application/json` - * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) - * - `Content-Type: application/json` - * - * To add or overwrite these defaults, simply add or remove a property from these configuration - * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object - * with the lowercased HTTP method name as the key, e.g. - * `$httpProvider.defaults.headers.get['My-Header']='value'`. - * - * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same - * fashion. - * - * - * # Transforming Requests and Responses - * - * Both requests and responses can be transformed using transform functions. By default, Angular - * applies these transformations: - * - * Request transformations: - * - * - If the `data` property of the request configuration object contains an object, serialize it into - * JSON format. - * - * Response transformations: - * - * - If XSRF prefix is detected, strip it (see Security Considerations section below). - * - If JSON response is detected, deserialize it using a JSON parser. - * - * To globally augment or override the default transforms, modify the `$httpProvider.defaults.transformRequest` and - * `$httpProvider.defaults.transformResponse` properties. These properties are by default an - * array of transform functions, which allows you to `push` or `unshift` a new transformation function into the - * transformation chain. You can also decide to completely override any default transformations by assigning your - * transformation functions to these properties directly without the array wrapper. - * - * Similarly, to locally override the request/response transforms, augment the `transformRequest` and/or - * `transformResponse` properties of the configuration object passed into `$http`. - * - * - * # Caching - * - * To enable caching, set the configuration property `cache` to `true`. When the cache is - * enabled, `$http` stores the response from the server in local cache. Next time the - * response is served from the cache without sending a request to the server. - * - * Note that even if the response is served from cache, delivery of the data is asynchronous in - * the same way that real requests are. - * - * If there are multiple GET requests for the same URL that should be cached using the same - * cache, but the cache is not populated yet, only one request to the server will be made and - * the remaining requests will be fulfilled using the response from the first request. - * - * - * # Response interceptors - * - * Before you start creating interceptors, be sure to understand the - * {@link ng.$q $q and deferred/promise APIs}. - * - * For purposes of global error handling, authentication or any kind of synchronous or - * asynchronous preprocessing of received responses, it is desirable to be able to intercept - * responses for http requests before they are handed over to the application code that - * initiated these requests. The response interceptors leverage the {@link ng.$q - * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. - * - * The interceptors are service factories that are registered with the $httpProvider by - * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and - * injected with dependencies (if specified) and returns the interceptor — a function that - * takes a {@link ng.$q promise} and returns the original or a new promise. - * - *
-     *   // register the interceptor as a service
-     *   $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
-     *     return function(promise) {
-     *       return promise.then(function(response) {
-     *         // do something on success
-     *       }, function(response) {
-     *         // do something on error
-     *         if (canRecover(response)) {
-     *           return responseOrNewPromise
-     *         }
-     *         return $q.reject(response);
-     *       });
-     *     }
-     *   });
-     *
-     *   $httpProvider.responseInterceptors.push('myHttpInterceptor');
-     *
-     *
-     *   // register the interceptor via an anonymous factory
-     *   $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
-     *     return function(promise) {
-     *       // same as above
-     *     }
-     *   });
-     * 
- * - * - * # Security Considerations - * - * When designing web applications, consider security threats from: - * - * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx - * JSON vulnerability} - * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} - * - * Both server and the client must cooperate in order to eliminate these threats. Angular comes - * pre-configured with strategies that address these issues, but for this to work backend server - * cooperation is required. - * - * ## JSON Vulnerability Protection - * - * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx - * JSON vulnerability} allows third party website to turn your JSON resource URL into - * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To - * counter this your server can prefix all JSON requests with following string `")]}',\n"`. - * Angular will automatically strip the prefix before processing it as JSON. - * - * For example if your server needs to return: - *
-     * ['one','two']
-     * 
- * - * which is vulnerable to attack, your server can return: - *
-     * )]}',
-     * ['one','two']
-     * 
- * - * Angular will strip the prefix, before processing the JSON. - * - * - * ## Cross Site Request Forgery (XSRF) Protection - * - * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which - * an unauthorized site can gain your user's private data. Angular provides a mechanism - * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie - * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that - * runs on your domain could read the cookie, your server can be assured that the XHR came from - * JavaScript running on your domain. - * - * To take advantage of this, your server needs to set a token in a JavaScript readable session - * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the - * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure - * that only JavaScript running on your domain could have sent the request. The token must be - * unique for each user and must be verifiable by the server (to prevent the JavaScript from making - * up its own tokens). We recommend that the token is a digest of your site's authentication - * cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} for added security. - * - * - * @param {object} config Object describing the request to be made and how it should be - * processed. The object has following properties: - * - * - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc) - * - **url** – `{string}` – Absolute or relative URL of the resource that is being requested. - * - **params** – `{Object.}` – Map of strings or objects which will be turned to - * `?key1=value1&key2=value2` after the url. If the value is not a string, it will be JSONified. - * - **data** – `{string|Object}` – Data to be sent as the request message data. - * - **headers** – `{Object}` – Map of strings representing HTTP headers to send to the server. - * - **transformRequest** – `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * request body and headers and returns its transformed (typically serialized) version. - * - **transformResponse** – `{function(data, headersGetter)|Array.}` – - * transform function or an array of such functions. The transform function takes the http - * response body and headers and returns its transformed (typically deserialized) version. - * - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the - * GET request, otherwise if a cache instance built with - * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for - * caching. - * - **timeout** – `{number}` – timeout in milliseconds. - * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the - * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 - * requests with credentials} for more information. - * - * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the - * standard `then` method and two http specific methods: `success` and `error`. The `then` - * method takes two arguments a success and an error callback which will be called with a - * response object. The `success` and `error` methods take a single argument - a function that - * will be called when the request succeeds or fails respectively. The arguments passed into - * these functions are destructured representation of the response object passed into the - * `then` method. The response object has these properties: - * - * - **data** – `{string|Object}` – The response body transformed with the transform functions. - * - **status** – `{number}` – HTTP status code of the response. - * - **headers** – `{function([headerName])}` – Header getter function. - * - **config** – `{Object}` – The configuration object that was used to generate the request. - * - * @property {Array.} pendingRequests Array of config objects for currently pending - * requests. This is primarily meant to be used for debugging purposes. - * - * - * @example - - -
- - -
- - - -
http status code: {{status}}
-
http response data: {{data}}
-
-
- - function FetchCtrl($scope, $http, $templateCache) { - $scope.method = 'GET'; - $scope.url = 'http-hello.html'; - - $scope.fetch = function() { - $scope.code = null; - $scope.response = null; - - $http({method: $scope.method, url: $scope.url, cache: $templateCache}). - success(function(data, status) { - $scope.status = status; - $scope.data = data; - }). - error(function(data, status) { - $scope.data = data || "Request failed"; - $scope.status = status; - }); - }; - - $scope.updateModel = function(method, url) { - $scope.method = method; - $scope.url = url; - }; - } - - - Hello, $http! - - - it('should make an xhr GET request', function() { - element(':button:contains("Sample GET")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('200'); - expect(binding('data')).toMatch(/Hello, \$http!/); - }); - - it('should make a JSONP request to angularjs.org', function() { - element(':button:contains("Sample JSONP")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('200'); - expect(binding('data')).toMatch(/Super Hero!/); - }); - - it('should make JSONP request to invalid URL and invoke the error handler', - function() { - element(':button:contains("Invalid JSONP")').click(); - element(':button:contains("fetch")').click(); - expect(binding('status')).toBe('0'); - expect(binding('data')).toBe('Request failed'); - }); - -
- */ - function $http(config) { - config.method = uppercase(config.method); - - var reqTransformFn = config.transformRequest || $config.transformRequest, - respTransformFn = config.transformResponse || $config.transformResponse, - defHeaders = $config.headers, - reqHeaders = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, - defHeaders.common, defHeaders[lowercase(config.method)], config.headers), - reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn), - promise; - - // strip content-type if data is undefined - if (isUndefined(config.data)) { - delete reqHeaders['Content-Type']; - } - - // send request - promise = sendReq(config, reqData, reqHeaders); - - - // transform future response - promise = promise.then(transformResponse, transformResponse); - - // apply interceptors - forEach(responseInterceptors, function(interceptor) { - promise = interceptor(promise); - }); - - promise.success = function(fn) { - promise.then(function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - promise.error = function(fn) { - promise.then(null, function(response) { - fn(response.data, response.status, response.headers, config); - }); - return promise; - }; - - return promise; - - function transformResponse(response) { - // make a copy since the response must be cacheable - var resp = extend({}, response, { - data: transformData(response.data, response.headers, respTransformFn) - }); - return (isSuccess(response.status)) - ? resp - : $q.reject(resp); - } - } - - $http.pendingRequests = []; - - /** - * @ngdoc method - * @name ng.$http#get - * @methodOf ng.$http - * - * @description - * Shortcut method to perform `GET` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name ng.$http#delete - * @methodOf ng.$http - * - * @description - * Shortcut method to perform `DELETE` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name ng.$http#head - * @methodOf ng.$http - * - * @description - * Shortcut method to perform `HEAD` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name ng.$http#jsonp - * @methodOf ng.$http - * - * @description - * Shortcut method to perform `JSONP` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request. - * Should contain `JSON_CALLBACK` string. - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethods('get', 'delete', 'head', 'jsonp'); - - /** - * @ngdoc method - * @name ng.$http#post - * @methodOf ng.$http - * - * @description - * Shortcut method to perform `POST` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - - /** - * @ngdoc method - * @name ng.$http#put - * @methodOf ng.$http - * - * @description - * Shortcut method to perform `PUT` request. - * - * @param {string} url Relative or absolute URL specifying the destination of the request - * @param {*} data Request content - * @param {Object=} config Optional configuration object - * @returns {HttpPromise} Future object - */ - createShortMethodsWithData('post', 'put'); - - /** - * @ngdoc property - * @name ng.$http#defaults - * @propertyOf ng.$http - * - * @description - * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of - * default headers as well as request and response transformations. - * - * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. - */ - $http.defaults = $config; - - - return $http; - - - function createShortMethods(names) { - forEach(arguments, function(name) { - $http[name] = function(url, config) { - return $http(extend(config || {}, { - method: name, - url: url - })); - }; - }); - } - - - function createShortMethodsWithData(name) { - forEach(arguments, function(name) { - $http[name] = function(url, data, config) { - return $http(extend(config || {}, { - method: name, - url: url, - data: data - })); - }; - }); - } - - - /** - * Makes the request. - * - * !!! ACCESSES CLOSURE VARS: - * $httpBackend, $config, $log, $rootScope, defaultCache, $http.pendingRequests - */ - function sendReq(config, reqData, reqHeaders) { - var deferred = $q.defer(), - promise = deferred.promise, - cache, - cachedResp, - url = buildUrl(config.url, config.params); - - $http.pendingRequests.push(config); - promise.then(removePendingReq, removePendingReq); - - - if (config.cache && config.method == 'GET') { - cache = isObject(config.cache) ? config.cache : defaultCache; - } - - if (cache) { - cachedResp = cache.get(url); - if (cachedResp) { - if (cachedResp.then) { - // cached request has already been sent, but there is no response yet - cachedResp.then(removePendingReq, removePendingReq); - return cachedResp; - } else { - // serving from cache - if (isArray(cachedResp)) { - resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); - } else { - resolvePromise(cachedResp, 200, {}); - } - } - } else { - // put the promise for the non-transformed response into cache as a placeholder - cache.put(url, promise); - } - } - - // if we won't have the response in cache, send the request to the backend - if (!cachedResp) { - $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, - config.withCredentials); - } - - return promise; - - - /** - * Callback registered to $httpBackend(): - * - caches the response if desired - * - resolves the raw $http promise - * - calls $apply - */ - function done(status, response, headersString) { - if (cache) { - if (isSuccess(status)) { - cache.put(url, [status, response, parseHeaders(headersString)]); - } else { - // remove promise from the cache - cache.remove(url); - } - } - - resolvePromise(response, status, headersString); - $rootScope.$apply(); - } - - - /** - * Resolves the raw $http promise. - */ - function resolvePromise(response, status, headers) { - // normalize internal statuses to 0 - status = Math.max(status, 0); - - (isSuccess(status) ? deferred.resolve : deferred.reject)({ - data: response, - status: status, - headers: headersGetter(headers), - config: config - }); - } - - - function removePendingReq() { - var idx = indexOf($http.pendingRequests, config); - if (idx !== -1) $http.pendingRequests.splice(idx, 1); - } - } - - - function buildUrl(url, params) { - if (!params) return url; - var parts = []; - forEachSorted(params, function(value, key) { - if (value == null || value == undefined) return; - if (isObject(value)) { - value = toJson(value); - } - parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); - }); - return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); - } - - - }]; -} - -var XHR = window.XMLHttpRequest || function() { - try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {} - try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {} - try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {} - throw new Error("This browser does not support XMLHttpRequest."); -}; - - -/** - * @ngdoc object - * @name ng.$httpBackend - * @requires $browser - * @requires $window - * @requires $document - * - * @description - * HTTP backend used by the {@link ng.$http service} that delegates to - * XMLHttpRequest object or JSONP and deals with browser incompatibilities. - * - * You should never need to use this service directly, instead use the higher-level abstractions: - * {@link ng.$http $http} or {@link ngResource.$resource $resource}. - * - * During testing this implementation is swapped with {@link ngMock.$httpBackend mock - * $httpBackend} which can be trained with responses. - */ -function $HttpBackendProvider() { - this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { - return createHttpBackend($browser, XHR, $browser.defer, $window.angular.callbacks, - $document[0], $window.location.protocol.replace(':', '')); - }]; -} - -function createHttpBackend($browser, XHR, $browserDefer, callbacks, rawDocument, locationProtocol) { - // TODO(vojta): fix the signature - return function(method, url, post, callback, headers, timeout, withCredentials) { - $browser.$$incOutstandingRequestCount(); - url = url || $browser.url(); - - if (lowercase(method) == 'jsonp') { - var callbackId = '_' + (callbacks.counter++).toString(36); - callbacks[callbackId] = function(data) { - callbacks[callbackId].data = data; - }; - - jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), - function() { - if (callbacks[callbackId].data) { - completeRequest(callback, 200, callbacks[callbackId].data); - } else { - completeRequest(callback, -2); - } - delete callbacks[callbackId]; - }); - } else { - var xhr = new XHR(); - xhr.open(method, url, true); - forEach(headers, function(value, key) { - if (value) xhr.setRequestHeader(key, value); - }); - - var status; - - // In IE6 and 7, this might be called synchronously when xhr.send below is called and the - // response is in the cache. the promise api will ensure that to the app code the api is - // always async - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - var responseHeaders = xhr.getAllResponseHeaders(); - - // TODO(vojta): remove once Firefox 21 gets released. - // begin: workaround to overcome Firefox CORS http response headers bug - // https://bugzilla.mozilla.org/show_bug.cgi?id=608735 - // Firefox already patched in nightly. Should land in Firefox 21. - - // CORS "simple response headers" http://www.w3.org/TR/cors/ - var value, - simpleHeaders = ["Cache-Control", "Content-Language", "Content-Type", - "Expires", "Last-Modified", "Pragma"]; - if (!responseHeaders) { - responseHeaders = ""; - forEach(simpleHeaders, function (header) { - var value = xhr.getResponseHeader(header); - if (value) { - responseHeaders += header + ": " + value + "\n"; - } - }); - } - // end of the workaround. - - completeRequest(callback, status || xhr.status, xhr.responseText, - responseHeaders); - } - }; - - if (withCredentials) { - xhr.withCredentials = true; - } - - xhr.send(post || ''); - - if (timeout > 0) { - $browserDefer(function() { - status = -1; - xhr.abort(); - }, timeout); - } - } - - - function completeRequest(callback, status, response, headersString) { - // URL_MATCH is defined in src/service/location.js - var protocol = (url.match(URL_MATCH) || ['', locationProtocol])[1]; - - // fix status code for file protocol (it's always 0) - status = (protocol == 'file') ? (response ? 200 : 404) : status; - - // normalize IE bug (http://bugs.jquery.com/ticket/1450) - status = status == 1223 ? 204 : status; - - callback(status, response, headersString); - $browser.$$completeOutstandingRequest(noop); - } - }; - - function jsonpReq(url, done) { - // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: - // - fetches local scripts via XHR and evals them - // - adds and immediately removes script elements from the document - var script = rawDocument.createElement('script'), - doneWrapper = function() { - rawDocument.body.removeChild(script); - if (done) done(); - }; - - script.type = 'text/javascript'; - script.src = url; - - if (msie) { - script.onreadystatechange = function() { - if (/loaded|complete/.test(script.readyState)) doneWrapper(); - }; - } else { - script.onload = script.onerror = doneWrapper; - } - - rawDocument.body.appendChild(script); - } -} - -/** - * @ngdoc object - * @name ng.$locale - * - * @description - * $locale service provides localization rules for various Angular components. As of right now the - * only public api is: - * - * * `id` – `{string}` – locale id formatted as `languageId-countryId` (e.g. `en-us`) - */ -function $LocaleProvider(){ - this.$get = function() { - return { - id: 'en-us', - - NUMBER_FORMATS: { - DECIMAL_SEP: '.', - GROUP_SEP: ',', - PATTERNS: [ - { // Decimal Pattern - minInt: 1, - minFrac: 0, - maxFrac: 3, - posPre: '', - posSuf: '', - negPre: '-', - negSuf: '', - gSize: 3, - lgSize: 3 - },{ //Currency Pattern - minInt: 1, - minFrac: 2, - maxFrac: 2, - posPre: '\u00A4', - posSuf: '', - negPre: '(\u00A4', - negSuf: ')', - gSize: 3, - lgSize: 3 - } - ], - CURRENCY_SYM: '$' - }, - - DATETIME_FORMATS: { - MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December' - .split(','), - SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), - DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), - SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), - AMPMS: ['AM','PM'], - medium: 'MMM d, y h:mm:ss a', - short: 'M/d/yy h:mm a', - fullDate: 'EEEE, MMMM d, y', - longDate: 'MMMM d, y', - mediumDate: 'MMM d, y', - shortDate: 'M/d/yy', - mediumTime: 'h:mm:ss a', - shortTime: 'h:mm a' - }, - - pluralCat: function(num) { - if (num === 1) { - return 'one'; - } - return 'other'; - } - }; - }; -} - -function $TimeoutProvider() { - this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', - function($rootScope, $browser, $q, $exceptionHandler) { - var deferreds = {}; - - - /** - * @ngdoc function - * @name ng.$timeout - * @requires $browser - * - * @description - * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch - * block and delegates any exceptions to - * {@link ng.$exceptionHandler $exceptionHandler} service. - * - * The return value of registering a timeout function is a promise, which will be resolved when - * the timeout is reached and the timeout function is executed. - * - * To cancel a timeout request, call `$timeout.cancel(promise)`. - * - * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to - * synchronously flush the queue of deferred functions. - * - * @param {function()} fn A function, whose execution should be delayed. - * @param {number=} [delay=0] Delay in milliseconds. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this - * promise will be resolved with is the return value of the `fn` function. - */ - function timeout(fn, delay, invokeApply) { - var deferred = $q.defer(), - promise = deferred.promise, - skipApply = (isDefined(invokeApply) && !invokeApply), - timeoutId, cleanup; - - timeoutId = $browser.defer(function() { - try { - deferred.resolve(fn()); - } catch(e) { - deferred.reject(e); - $exceptionHandler(e); - } - - if (!skipApply) $rootScope.$apply(); - }, delay); - - cleanup = function() { - delete deferreds[promise.$$timeoutId]; - }; - - promise.$$timeoutId = timeoutId; - deferreds[timeoutId] = deferred; - promise.then(cleanup, cleanup); - - return promise; - } - - - /** - * @ngdoc function - * @name ng.$timeout#cancel - * @methodOf ng.$timeout - * - * @description - * Cancels a task associated with the `promise`. As a result of this, the promise will be - * resolved with a rejection. - * - * @param {Promise=} promise Promise returned by the `$timeout` function. - * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully - * canceled. - */ - timeout.cancel = function(promise) { - if (promise && promise.$$timeoutId in deferreds) { - deferreds[promise.$$timeoutId].reject('canceled'); - return $browser.defer.cancel(promise.$$timeoutId); - } - return false; - }; - - return timeout; - }]; -} - -/** - * @ngdoc object - * @name ng.$filterProvider - * @description - * - * Filters are just functions which transform input to an output. However filters need to be Dependency Injected. To - * achieve this a filter definition consists of a factory function which is annotated with dependencies and is - * responsible for creating a filter function. - * - *
- *   // Filter registration
- *   function MyModule($provide, $filterProvider) {
- *     // create a service to demonstrate injection (not always needed)
- *     $provide.value('greet', function(name){
- *       return 'Hello ' + name + '!';
- *     });
- *
- *     // register a filter factory which uses the
- *     // greet service to demonstrate DI.
- *     $filterProvider.register('greet', function(greet){
- *       // return the filter function which uses the greet service
- *       // to generate salutation
- *       return function(text) {
- *         // filters need to be forgiving so check input validity
- *         return text && greet(text) || text;
- *       };
- *     });
- *   }
- * 
- * - * The filter function is registered with the `$injector` under the filter name suffixe with `Filter`. - *
- *   it('should be the same instance', inject(
- *     function($filterProvider) {
- *       $filterProvider.register('reverse', function(){
- *         return ...;
- *       });
- *     },
- *     function($filter, reverseFilter) {
- *       expect($filter('reverse')).toBe(reverseFilter);
- *     });
- * 
- * - * - * For more information about how angular filters work, and how to create your own filters, see - * {@link guide/dev_guide.templates.filters Understanding Angular Filters} in the angular Developer - * Guide. - */ -/** - * @ngdoc method - * @name ng.$filterProvider#register - * @methodOf ng.$filterProvider - * @description - * Register filter factory function. - * - * @param {String} name Name of the filter. - * @param {function} fn The filter factory function which is injectable. - */ - - -/** - * @ngdoc function - * @name ng.$filter - * @function - * @description - * Filters are used for formatting data displayed to the user. - * - * The general syntax in templates is as follows: - * - * {{ expression [| filter_name[:parameter_value] ... ] }} - * - * @param {String} name Name of the filter function to retrieve - * @return {Function} the filter function - */ -$FilterProvider.$inject = ['$provide']; -function $FilterProvider($provide) { - var suffix = 'Filter'; - - function register(name, factory) { - return $provide.factory(name + suffix, factory); - } - this.register = register; - - this.$get = ['$injector', function($injector) { - return function(name) { - return $injector.get(name + suffix); - } - }]; - - //////////////////////////////////////// - - register('currency', currencyFilter); - register('date', dateFilter); - register('filter', filterFilter); - register('json', jsonFilter); - register('limitTo', limitToFilter); - register('lowercase', lowercaseFilter); - register('number', numberFilter); - register('orderBy', orderByFilter); - register('uppercase', uppercaseFilter); -} - -/** - * @ngdoc filter - * @name ng.filter:filter - * @function - * - * @description - * Selects a subset of items from `array` and returns it as a new array. - * - * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link ng.$filter} for more information about Angular arrays. - * - * @param {Array} array The source array. - * @param {string|Object|function()} expression The predicate to be used for selecting items from - * `array`. - * - * Can be one of: - * - * - `string`: Predicate that results in a substring match using the value of `expression` - * string. All strings or objects with string properties in `array` that contain this string - * will be returned. The predicate can be negated by prefixing the string with `!`. - * - * - `Object`: A pattern object can be used to filter specific properties on objects contained - * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items - * which have property `name` containing "M" and property `phone` containing "1". A special - * property name `$` can be used (as in `{$:"text"}`) to accept a match against any - * property of the object. That's equivalent to the simple substring match with a `string` - * as described above. - * - * - `function`: A predicate function can be used to write arbitrary filters. The function is - * called for each element of `array`. The final result is an array of those elements that - * the predicate returned true for. - * - * @example - - -
- - Search: - - - - - - -
NamePhone
{{friend.name}}{{friend.phone}}
-
- Any:
- Name only
- Phone only
- - - - - - -
NamePhone
{{friend.name}}{{friend.phone}}
-
- - it('should search across all fields when filtering with a string', function() { - input('searchText').enter('m'); - expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). - toEqual(['Mary', 'Mike', 'Adam']); - - input('searchText').enter('76'); - expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). - toEqual(['John', 'Julie']); - }); - - it('should search in specific fields when filtering with a predicate object', function() { - input('search.$').enter('i'); - expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). - toEqual(['Mary', 'Mike', 'Julie']); - }); - -
- */ -function filterFilter() { - return function(array, expression) { - if (!isArray(array)) return array; - var predicates = []; - predicates.check = function(value) { - for (var j = 0; j < predicates.length; j++) { - if(!predicates[j](value)) { - return false; - } - } - return true; - }; - var search = function(obj, text){ - if (text.charAt(0) === '!') { - return !search(obj, text.substr(1)); - } - switch (typeof obj) { - case "boolean": - case "number": - case "string": - return ('' + obj).toLowerCase().indexOf(text) > -1; - case "object": - for ( var objKey in obj) { - if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { - return true; - } - } - return false; - case "array": - for ( var i = 0; i < obj.length; i++) { - if (search(obj[i], text)) { - return true; - } - } - return false; - default: - return false; - } - }; - switch (typeof expression) { - case "boolean": - case "number": - case "string": - expression = {$:expression}; - case "object": - for (var key in expression) { - if (key == '$') { - (function() { - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(value, text); - }); - })(); - } else { - (function() { - var path = key; - var text = (''+expression[key]).toLowerCase(); - if (!text) return; - predicates.push(function(value) { - return search(getter(value, path), text); - }); - })(); - } - } - break; - case 'function': - predicates.push(expression); - break; - default: - return array; - } - var filtered = []; - for ( var j = 0; j < array.length; j++) { - var value = array[j]; - if (predicates.check(value)) { - filtered.push(value); - } - } - return filtered; - } -} - -/** - * @ngdoc filter - * @name ng.filter:currency - * @function - * - * @description - * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default - * symbol for current locale is used. - * - * @param {number} amount Input to filter. - * @param {string=} symbol Currency symbol or identifier to be displayed. - * @returns {string} Formatted number. - * - * - * @example - - - -
-
- default currency symbol ($): {{amount | currency}}
- custom currency identifier (USD$): {{amount | currency:"USD$"}} -
-
- - it('should init with 1234.56', function() { - expect(binding('amount | currency')).toBe('$1,234.56'); - expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56'); - }); - it('should update', function() { - input('amount').enter('-1234'); - expect(binding('amount | currency')).toBe('($1,234.00)'); - expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)'); - }); - -
- */ -currencyFilter.$inject = ['$locale']; -function currencyFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(amount, currencySymbol){ - if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; - return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). - replace(/\u00A4/g, currencySymbol); - }; -} - -/** - * @ngdoc filter - * @name ng.filter:number - * @function - * - * @description - * Formats a number as text. - * - * If the input is not a number an empty string is returned. - * - * @param {number|string} number Number to format. - * @param {(number|string)=} [fractionSize=2] Number of decimal places to round the number to. - * @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit. - * - * @example - - - -
- Enter number:
- Default formatting: {{val | number}}
- No fractions: {{val | number:0}}
- Negative number: {{-val | number:4}} -
-
- - it('should format numbers', function() { - expect(binding('val | number')).toBe('1,234.568'); - expect(binding('val | number:0')).toBe('1,235'); - expect(binding('-val | number:4')).toBe('-1,234.5679'); - }); - - it('should update', function() { - input('val').enter('3374.333'); - expect(binding('val | number')).toBe('3,374.333'); - expect(binding('val | number:0')).toBe('3,374'); - expect(binding('-val | number:4')).toBe('-3,374.3330'); - }); - -
- */ - - -numberFilter.$inject = ['$locale']; -function numberFilter($locale) { - var formats = $locale.NUMBER_FORMATS; - return function(number, fractionSize) { - return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, - fractionSize); - }; -} - -var DECIMAL_SEP = '.'; -function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { - if (isNaN(number) || !isFinite(number)) return ''; - - var isNegative = number < 0; - number = Math.abs(number); - var numStr = number + '', - formatedText = '', - parts = []; - - var hasExponent = false; - if (numStr.indexOf('e') !== -1) { - var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); - if (match && match[2] == '-' && match[3] > fractionSize + 1) { - numStr = '0'; - } else { - formatedText = numStr; - hasExponent = true; - } - } - - if (!hasExponent) { - var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; - - // determine fractionSize if it is not specified - if (isUndefined(fractionSize)) { - fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); - } - - var pow = Math.pow(10, fractionSize); - number = Math.round(number * pow) / pow; - var fraction = ('' + number).split(DECIMAL_SEP); - var whole = fraction[0]; - fraction = fraction[1] || ''; - - var pos = 0, - lgroup = pattern.lgSize, - group = pattern.gSize; - - if (whole.length >= (lgroup + group)) { - pos = whole.length - lgroup; - for (var i = 0; i < pos; i++) { - if ((pos - i)%group === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - } - - for (i = pos; i < whole.length; i++) { - if ((whole.length - i)%lgroup === 0 && i !== 0) { - formatedText += groupSep; - } - formatedText += whole.charAt(i); - } - - // format fraction part. - while(fraction.length < fractionSize) { - fraction += '0'; - } - - if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); - } - - parts.push(isNegative ? pattern.negPre : pattern.posPre); - parts.push(formatedText); - parts.push(isNegative ? pattern.negSuf : pattern.posSuf); - return parts.join(''); -} - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while(num.length < digits) num = '0' + num; - if (trim) - num = num.substr(num.length - digits); - return neg + num; -} - - -function dateGetter(name, size, offset, trim) { - offset = offset || 0; - return function(date) { - var value = date['get' + name](); - if (offset > 0 || value > -offset) - value += offset; - if (value === 0 && offset == -12 ) value = 12; - return padNumber(value, size, trim); - }; -} - -function dateStrGetter(name, shortForm) { - return function(date, formats) { - var value = date['get' + name](); - var get = uppercase(shortForm ? ('SHORT' + name) : name); - - return formats[get][value]; - }; -} - -function timeZoneGetter(date) { - var zone = -1 * date.getTimezoneOffset(); - var paddedZone = (zone >= 0) ? "+" : ""; - - paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + - padNumber(Math.abs(zone % 60), 2); - - return paddedZone; -} - -function ampmGetter(date, formats) { - return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; -} - -var DATE_FORMATS = { - yyyy: dateGetter('FullYear', 4), - yy: dateGetter('FullYear', 2, 0, true), - y: dateGetter('FullYear', 1), - MMMM: dateStrGetter('Month'), - MMM: dateStrGetter('Month', true), - MM: dateGetter('Month', 2, 1), - M: dateGetter('Month', 1, 1), - dd: dateGetter('Date', 2), - d: dateGetter('Date', 1), - HH: dateGetter('Hours', 2), - H: dateGetter('Hours', 1), - hh: dateGetter('Hours', 2, -12), - h: dateGetter('Hours', 1, -12), - mm: dateGetter('Minutes', 2), - m: dateGetter('Minutes', 1), - ss: dateGetter('Seconds', 2), - s: dateGetter('Seconds', 1), - EEEE: dateStrGetter('Day'), - EEE: dateStrGetter('Day', true), - a: ampmGetter, - Z: timeZoneGetter -}; - -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, - NUMBER_STRING = /^\d+$/; - -/** - * @ngdoc filter - * @name ng.filter:date - * @function - * - * @description - * Formats `date` to a string based on the requested `format`. - * - * `format` string can be composed of the following elements: - * - * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) - * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) - * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) - * * `'MMMM'`: Month in year (January-December) - * * `'MMM'`: Month in year (Jan-Dec) - * * `'MM'`: Month in year, padded (01-12) - * * `'M'`: Month in year (1-12) - * * `'dd'`: Day in month, padded (01-31) - * * `'d'`: Day in month (1-31) - * * `'EEEE'`: Day in Week,(Sunday-Saturday) - * * `'EEE'`: Day in Week, (Sun-Sat) - * * `'HH'`: Hour in day, padded (00-23) - * * `'H'`: Hour in day (0-23) - * * `'hh'`: Hour in am/pm, padded (01-12) - * * `'h'`: Hour in am/pm, (1-12) - * * `'mm'`: Minute in hour, padded (00-59) - * * `'m'`: Minute in hour (0-59) - * * `'ss'`: Second in minute, padded (00-59) - * * `'s'`: Second in minute (0-59) - * * `'a'`: am/pm marker - * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) - * - * `format` string can also be one of the following predefined - * {@link guide/i18n localizable formats}: - * - * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale - * (e.g. Sep 3, 2010 12:05:08 pm) - * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) - * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale - * (e.g. Friday, September 3, 2010) - * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010 - * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) - * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) - * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) - * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) - * - * `format` string can contain literal values. These need to be quoted with single quotes (e.g. - * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence - * (e.g. `"h o''clock"`). - * - * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or - * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its - * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is - * specified in the string input, the time is considered to be in the local timezone. - * @param {string=} format Formatting rules (see Description). If not specified, - * `mediumDate` is used. - * @returns {string} Formatted string or the input if input is not recognized as date/millis. - * - * @example - - - {{1288323623006 | date:'medium'}}: - {{1288323623006 | date:'medium'}}
- {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}: - {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
- {{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}: - {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
-
- - it('should format date', function() { - expect(binding("1288323623006 | date:'medium'")). - toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); - expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). - toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); - expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). - toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); - }); - -
- */ -dateFilter.$inject = ['$locale']; -function dateFilter($locale) { - - - var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; - function jsonStringToDate(string){ - var match; - if (match = string.match(R_ISO8601_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0; - if (match[9]) { - tzHour = int(match[9] + match[10]); - tzMin = int(match[9] + match[11]); - } - date.setUTCFullYear(int(match[1]), int(match[2]) - 1, int(match[3])); - date.setUTCHours(int(match[4]||0) - tzHour, int(match[5]||0) - tzMin, int(match[6]||0), int(match[7]||0)); - return date; - } - return string; - } - - - return function(date, format) { - var text = '', - parts = [], - fn, match; - - format = format || 'mediumDate'; - format = $locale.DATETIME_FORMATS[format] || format; - if (isString(date)) { - if (NUMBER_STRING.test(date)) { - date = int(date); - } else { - date = jsonStringToDate(date); - } - } - - if (isNumber(date)) { - date = new Date(date); - } - - if (!isDate(date)) { - return date; - } - - while(format) { - match = DATE_FORMATS_SPLIT.exec(format); - if (match) { - parts = concat(parts, match, 1); - format = parts.pop(); - } else { - parts.push(format); - format = null; - } - } - - forEach(parts, function(value){ - fn = DATE_FORMATS[value]; - text += fn ? fn(date, $locale.DATETIME_FORMATS) - : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); - }); - - return text; - }; -} - - -/** - * @ngdoc filter - * @name ng.filter:json - * @function - * - * @description - * Allows you to convert a JavaScript object into JSON string. - * - * This filter is mostly useful for debugging. When using the double curly {{value}} notation - * the binding is automatically converted to JSON. - * - * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. - * @returns {string} JSON string. - * - * - * @example: - - -
{{ {'name':'value'} | json }}
-
- - it('should jsonify filtered objects', function() { - expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/); - }); - -
- * - */ -function jsonFilter() { - return function(object) { - return toJson(object, true); - }; -} - - -/** - * @ngdoc filter - * @name ng.filter:lowercase - * @function - * @description - * Converts string to lowercase. - * @see angular.lowercase - */ -var lowercaseFilter = valueFn(lowercase); - - -/** - * @ngdoc filter - * @name ng.filter:uppercase - * @function - * @description - * Converts string to uppercase. - * @see angular.uppercase - */ -var uppercaseFilter = valueFn(uppercase); - -/** - * @ngdoc function - * @name ng.filter:limitTo - * @function - * - * @description - * Creates a new array containing only a specified number of elements in an array. The elements - * are taken from either the beginning or the end of the source array, as specified by the - * value and sign (positive or negative) of `limit`. - * - * Note: This function is used to augment the `Array` type in Angular expressions. See - * {@link ng.$filter} for more information about Angular arrays. - * - * @param {Array} array Source array to be limited. - * @param {string|Number} limit The length of the returned array. If the `limit` number is - * positive, `limit` number of items from the beginning of the source array are copied. - * If the number is negative, `limit` number of items from the end of the source array are - * copied. The `limit` will be trimmed if it exceeds `array.length` - * @returns {Array} A new sub-array of length `limit` or less if input array had less than `limit` - * elements. - * - * @example - - - -
- Limit {{numbers}} to: -

Output: {{ numbers | limitTo:limit }}

-
-
- - it('should limit the numer array to first three items', function() { - expect(element('.doc-example-live input[ng-model=limit]').val()).toBe('3'); - expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3]'); - }); - - it('should update the output when -3 is entered', function() { - input('limit').enter(-3); - expect(binding('numbers | limitTo:limit')).toEqual('[7,8,9]'); - }); - - it('should not exceed the maximum size of input array', function() { - input('limit').enter(100); - expect(binding('numbers | limitTo:limit')).toEqual('[1,2,3,4,5,6,7,8,9]'); - }); - -
- */ -function limitToFilter(){ - return function(array, limit) { - if (!(array instanceof Array)) return array; - limit = int(limit); - var out = [], - i, n; - - // check that array is iterable - if (!array || !(array instanceof Array)) - return out; - - // if abs(limit) exceeds maximum length, trim it - if (limit > array.length) - limit = array.length; - else if (limit < -array.length) - limit = -array.length; - - if (limit > 0) { - i = 0; - n = limit; - } else { - i = array.length + limit; - n = array.length; - } - - for (; i} expression A predicate to be - * used by the comparator to determine the order of elements. - * - * Can be one of: - * - * - `function`: Getter function. The result of this function will be sorted using the - * `<`, `=`, `>` operator. - * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' - * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control - * ascending or descending sort order (for example, +name or -name). - * - `Array`: An array of function or string predicates. The first predicate in the array - * is used for sorting, but when two items are equivalent, the next predicate is used. - * - * @param {boolean=} reverse Reverse the order the array. - * @returns {Array} Sorted copy of the source array. - * - * @example - - - -
-
Sorting predicate = {{predicate}}; reverse = {{reverse}}
-
- [ unsorted ] - - - - - - - - - - - -
Name - (^)Phone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
-
-
- - it('should be reverse ordered by aged', function() { - expect(binding('predicate')).toBe('-age'); - expect(repeater('table.friend', 'friend in friends').column('friend.age')). - toEqual(['35', '29', '21', '19', '10']); - expect(repeater('table.friend', 'friend in friends').column('friend.name')). - toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); - }); - - it('should reorder the table when user selects different predicate', function() { - element('.doc-example-live a:contains("Name")').click(); - expect(repeater('table.friend', 'friend in friends').column('friend.name')). - toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); - expect(repeater('table.friend', 'friend in friends').column('friend.age')). - toEqual(['35', '10', '29', '19', '21']); - - element('.doc-example-live a:contains("Phone")').click(); - expect(repeater('table.friend', 'friend in friends').column('friend.phone')). - toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); - expect(repeater('table.friend', 'friend in friends').column('friend.name')). - toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); - }); - -
- */ -orderByFilter.$inject = ['$parse']; -function orderByFilter($parse){ - return function(array, sortPredicate, reverseOrder) { - if (!isArray(array)) return array; - if (!sortPredicate) return array; - sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; - sortPredicate = map(sortPredicate, function(predicate){ - var descending = false, get = predicate || identity; - if (isString(predicate)) { - if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { - descending = predicate.charAt(0) == '-'; - predicate = predicate.substring(1); - } - get = $parse(predicate); - } - return reverseComparator(function(a,b){ - return compare(get(a),get(b)); - }, descending); - }); - var arrayCopy = []; - for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } - return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); - - function comparator(o1, o2){ - for ( var i = 0; i < sortPredicate.length; i++) { - var comp = sortPredicate[i](o1, o2); - if (comp !== 0) return comp; - } - return 0; - } - function reverseComparator(comp, descending) { - return toBoolean(descending) - ? function(a,b){return comp(b,a);} - : comp; - } - function compare(v1, v2){ - var t1 = typeof v1; - var t2 = typeof v2; - if (t1 == t2) { - if (t1 == "string") v1 = v1.toLowerCase(); - if (t1 == "string") v2 = v2.toLowerCase(); - if (v1 === v2) return 0; - return v1 < v2 ? -1 : 1; - } else { - return t1 < t2 ? -1 : 1; - } - } - } -} - -function ngDirective(directive) { - if (isFunction(directive)) { - directive = { - link: directive - } - } - directive.restrict = directive.restrict || 'AC'; - return valueFn(directive); -} - -/** - * @ngdoc directive - * @name ng.directive:a - * @restrict E - * - * @description - * Modifies the default behavior of html A tag, so that the default action is prevented when href - * attribute is empty. - * - * The reasoning for this change is to allow easy creation of action links with `ngClick` directive - * without changing the location or causing page reloads, e.g.: - * `Save` - */ -var htmlAnchorDirective = valueFn({ - restrict: 'E', - compile: function(element, attr) { - - if (msie <= 8) { - - // turn link into a stylable link in IE - // but only if it doesn't have name attribute, in which case it's an anchor - if (!attr.href && !attr.name) { - attr.$set('href', ''); - } - - // add a comment node to anchors to workaround IE bug that causes element content to be reset - // to new attribute content if attribute is updated with value containing @ and element also - // contains value with @ - // see issue #1949 - element.append(document.createComment('IE fix')); - } - - return function(scope, element) { - element.bind('click', function(event){ - // if we have no href url, then don't navigate anywhere. - if (!element.attr('href')) { - event.preventDefault(); - } - }); - } - } -}); - -/** - * @ngdoc directive - * @name ng.directive:ngHref - * @restrict A - * - * @description - * Using Angular markup like {{hash}} in an href attribute makes - * the page open to a wrong URL, if the user clicks that link before - * angular has a chance to replace the {{hash}} with actual URL, the - * link will be broken and will most likely return a 404 error. - * The `ngHref` directive solves this problem. - * - * The buggy way to write it: - *
- * 
- * 
- * - * The correct way to write it: - *
- * 
- * 
- * - * @element A - * @param {template} ngHref any string which can contain `{{}}` markup. - * - * @example - * This example uses `link` variable inside `href` attribute: - - -
-
link 1 (link, don't reload)
- link 2 (link, don't reload)
- link 3 (link, reload!)
- anchor (link, don't reload)
- anchor (no link)
- link (link, change location) - - - it('should execute ng-click but not reload when href without value', function() { - element('#link-1').click(); - expect(input('value').val()).toEqual('1'); - expect(element('#link-1').attr('href')).toBe(""); - }); - - it('should execute ng-click but not reload when href empty string', function() { - element('#link-2').click(); - expect(input('value').val()).toEqual('2'); - expect(element('#link-2').attr('href')).toBe(""); - }); - - it('should execute ng-click and change url when ng-href specified', function() { - expect(element('#link-3').attr('href')).toBe("/123"); - - element('#link-3').click(); - expect(browser().window().path()).toEqual('/123'); - }); - - it('should execute ng-click but not reload when href empty string and name specified', function() { - element('#link-4').click(); - expect(input('value').val()).toEqual('4'); - expect(element('#link-4').attr('href')).toBe(''); - }); - - it('should execute ng-click but not reload when no href but name specified', function() { - element('#link-5').click(); - expect(input('value').val()).toEqual('5'); - expect(element('#link-5').attr('href')).toBe(undefined); - }); - - it('should only change url when only ng-href', function() { - input('value').enter('6'); - expect(element('#link-6').attr('href')).toBe('6'); - - element('#link-6').click(); - expect(browser().location().url()).toEqual('/6'); - }); - - - */ - -/** - * @ngdoc directive - * @name ng.directive:ngSrc - * @restrict A - * - * @description - * Using Angular markup like `{{hash}}` in a `src` attribute doesn't - * work right: The browser will fetch from the URL with the literal - * text `{{hash}}` until Angular replaces the expression inside - * `{{hash}}`. The `ngSrc` directive solves this problem. - * - * The buggy way to write it: - *
- * 
- * 
- * - * The correct way to write it: - *
- * 
- * 
- * - * @element IMG - * @param {template} ngSrc any string which can contain `{{}}` markup. - */ - -/** - * @ngdoc directive - * @name ng.directive:ngDisabled - * @restrict A - * - * @description - * - * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: - *
- * 
- * - *
- *
- * - * The HTML specs do not require browsers to preserve the special attributes such as disabled. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngDisabled` directive. - * - * @example - - - Click me to toggle:
- -
- - it('should toggle button', function() { - expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); - }); - -
- * - * @element INPUT - * @param {expression} ngDisabled Angular expression that will be evaluated. - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngChecked - * @restrict A - * - * @description - * The HTML specs do not require browsers to preserve the special attributes such as checked. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngChecked` directive. - * @example - - - Check me to check both:
- -
- - it('should check both checkBoxes', function() { - expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy(); - input('master').check(); - expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy(); - }); - -
- * - * @element INPUT - * @param {expression} ngChecked Angular expression that will be evaluated. - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngMultiple - * @restrict A - * - * @description - * The HTML specs do not require browsers to preserve the special attributes such as multiple. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngMultiple` directive. - * - * @example - - - Check me check multiple:
- -
- - it('should toggle multiple', function() { - expect(element('.doc-example-live #select').prop('multiple')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live #select').prop('multiple')).toBeTruthy(); - }); - -
- * - * @element SELECT - * @param {expression} ngMultiple Angular expression that will be evaluated. - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngReadonly - * @restrict A - * - * @description - * The HTML specs do not require browsers to preserve the special attributes such as readonly. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduce the `ngReadonly` directive. - * @example - - - Check me to make text readonly:
- -
- - it('should toggle readonly attr', function() { - expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy(); - input('checked').check(); - expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy(); - }); - -
- * - * @element INPUT - * @param {string} expression Angular expression that will be evaluated. - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngSelected - * @restrict A - * - * @description - * The HTML specs do not require browsers to preserve the special attributes such as selected. - * (The presence of them means true and absence means false) - * This prevents the angular compiler from correctly retrieving the binding expression. - * To solve this problem, we introduced the `ngSelected` directive. - * @example - - - Check me to select:
- -
- - it('should select Greetings!', function() { - expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); - input('selected').check(); - expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); - }); - -
- * - * @element OPTION - * @param {string} expression Angular expression that will be evaluated. - */ - - -var ngAttributeAliasDirectives = {}; - - -// boolean attrs are evaluated -forEach(BOOLEAN_ATTR, function(propName, attrName) { - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - priority: 100, - compile: function() { - return function(scope, element, attr) { - scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { - attr.$set(attrName, !!value); - }); - }; - } - }; - }; -}); - - -// ng-src, ng-href are interpolated -forEach(['src', 'href'], function(attrName) { - var normalized = directiveNormalize('ng-' + attrName); - ngAttributeAliasDirectives[normalized] = function() { - return { - priority: 99, // it needs to run after the attributes are interpolated - link: function(scope, element, attr) { - attr.$observe(normalized, function(value) { - if (!value) - return; - - attr.$set(attrName, value); - - // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist - // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need - // to set the property as well to achieve the desired effect. - // we use attr[attrName] value since $set can sanitize the url. - if (msie) element.prop(attrName, attr[attrName]); - }); - } - }; - }; -}); - -var nullFormCtrl = { - $addControl: noop, - $removeControl: noop, - $setValidity: noop, - $setDirty: noop -}; - -/** - * @ngdoc object - * @name ng.directive:form.FormController - * - * @property {boolean} $pristine True if user has not interacted with the form yet. - * @property {boolean} $dirty True if user has already interacted with the form. - * @property {boolean} $valid True if all of the containing forms and controls are valid. - * @property {boolean} $invalid True if at least one containing control or form is invalid. - * - * @property {Object} $error Is an object hash, containing references to all invalid controls or - * forms, where: - * - * - keys are validation tokens (error names) — such as `required`, `url` or `email`), - * - values are arrays of controls or forms that are invalid with given error. - * - * @description - * `FormController` keeps track of all its controls and nested forms as well as state of them, - * such as being valid/invalid or dirty/pristine. - * - * Each {@link ng.directive:form form} directive creates an instance - * of `FormController`. - * - */ -//asks for $scope to fool the BC controller module -FormController.$inject = ['$element', '$attrs', '$scope']; -function FormController(element, attrs) { - var form = this, - parentForm = element.parent().controller('form') || nullFormCtrl, - invalidCount = 0, // used to easily determine if we are valid - errors = form.$error = {}; - - // init state - form.$name = attrs.name; - form.$dirty = false; - form.$pristine = true; - form.$valid = true; - form.$invalid = false; - - parentForm.$addControl(form); - - // Setup initial state of the control - element.addClass(PRISTINE_CLASS); - toggleValidCss(true); - - // convenience method for easy toggling of classes - function toggleValidCss(isValid, validationErrorKey) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - element. - removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). - addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); - } - - form.$addControl = function(control) { - if (control.$name && !form.hasOwnProperty(control.$name)) { - form[control.$name] = control; - } - }; - - form.$removeControl = function(control) { - if (control.$name && form[control.$name] === control) { - delete form[control.$name]; - } - forEach(errors, function(queue, validationToken) { - form.$setValidity(validationToken, true, control); - }); - }; - - form.$setValidity = function(validationToken, isValid, control) { - var queue = errors[validationToken]; - - if (isValid) { - if (queue) { - arrayRemove(queue, control); - if (!queue.length) { - invalidCount--; - if (!invalidCount) { - toggleValidCss(isValid); - form.$valid = true; - form.$invalid = false; - } - errors[validationToken] = false; - toggleValidCss(true, validationToken); - parentForm.$setValidity(validationToken, true, form); - } - } - - } else { - if (!invalidCount) { - toggleValidCss(isValid); - } - if (queue) { - if (includes(queue, control)) return; - } else { - errors[validationToken] = queue = []; - invalidCount++; - toggleValidCss(false, validationToken); - parentForm.$setValidity(validationToken, false, form); - } - queue.push(control); - - form.$valid = false; - form.$invalid = true; - } - }; - - form.$setDirty = function() { - element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); - form.$dirty = true; - form.$pristine = false; - parentForm.$setDirty(); - }; - -} - - -/** - * @ngdoc directive - * @name ng.directive:ngForm - * @restrict EAC - * - * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. - * - * @param {string=} name|ngForm Name of the form. If specified, the form controller will be published into - * related scope, under this name. - * - */ - - /** - * @ngdoc directive - * @name ng.directive:form - * @restrict E - * - * @description - * Directive that instantiates - * {@link ng.directive:form.FormController FormController}. - * - * If `name` attribute is specified, the form controller is published onto the current scope under - * this name. - * - * # Alias: {@link ng.directive:ngForm `ngForm`} - * - * In angular forms can be nested. This means that the outer form is valid when all of the child - * forms are valid as well. However browsers do not allow nesting of `
` elements, for this - * reason angular provides {@link ng.directive:ngForm `ngForm`} alias - * which behaves identical to `` but allows form nesting. - * - * - * # CSS classes - * - `ng-valid` Is set if the form is valid. - * - `ng-invalid` Is set if the form is invalid. - * - `ng-pristine` Is set if the form is pristine. - * - `ng-dirty` Is set if the form is dirty. - * - * - * # Submitting a form and preventing default action - * - * Since the role of forms in client-side Angular applications is different than in classical - * roundtrip apps, it is desirable for the browser not to translate the form submission into a full - * page reload that sends the data to the server. Instead some javascript logic should be triggered - * to handle the form submission in application specific way. - * - * For this reason, Angular prevents the default action (form submission to the server) unless the - * `` element has an `action` attribute specified. - * - * You can use one of the following two ways to specify what javascript method should be called when - * a form is submitted: - * - * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element - * - {@link ng.directive:ngClick ngClick} directive on the first - * button or input field of type submit (input[type=submit]) - * - * To prevent double execution of the handler, use only one of ngSubmit or ngClick directives. This - * is because of the following form submission rules coming from the html spec: - * - * - If a form has only one input field then hitting enter in this field triggers form submit - * (`ngSubmit`) - * - if a form has has 2+ input fields and no buttons or input[type=submit] then hitting enter - * doesn't trigger submit - * - if a form has one or more input fields and one or more buttons or input[type=submit] then - * hitting enter in any of the input fields will trigger the click handler on the *first* button or - * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) - * - * @param {string=} name Name of the form. If specified, the form controller will be published into - * related scope, under this name. - * - * @example - - - - - userType: - Required!
- userType = {{userType}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- -
- - it('should initialize to model', function() { - expect(binding('userType')).toEqual('guest'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - input('userType').enter(''); - expect(binding('userType')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - -
- */ -var formDirectiveFactory = function(isNgForm) { - return ['$timeout', function($timeout) { - var formDirective = { - name: 'form', - restrict: 'E', - controller: FormController, - compile: function() { - return { - pre: function(scope, formElement, attr, controller) { - if (!attr.action) { - // we can't use jq events because if a form is destroyed during submission the default - // action is not prevented. see #1238 - // - // IE 9 is not affected because it doesn't fire a submit event and try to do a full - // page reload if the form was destroyed by submission of the form via a click handler - // on a button in the form. Looks like an IE9 specific bug. - var preventDefaultListener = function(event) { - event.preventDefault - ? event.preventDefault() - : event.returnValue = false; // IE - }; - - addEventListenerFn(formElement[0], 'submit', preventDefaultListener); - - // unregister the preventDefault listener so that we don't not leak memory but in a - // way that will achieve the prevention of the default action. - formElement.bind('$destroy', function() { - $timeout(function() { - removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); - }, 0, false); - }); - } - - var parentFormCtrl = formElement.parent().controller('form'), - alias = attr.name || attr.ngForm; - - if (alias) { - scope[alias] = controller; - } - if (parentFormCtrl) { - formElement.bind('$destroy', function() { - parentFormCtrl.$removeControl(controller); - if (alias) { - scope[alias] = undefined; - } - extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards - }); - } - } - }; - } - }; - - return isNgForm ? extend(copy(formDirective), {restrict: 'EAC'}) : formDirective; - }]; -}; - -var formDirective = formDirectiveFactory(); -var ngFormDirective = formDirectiveFactory(true); - -var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; -var EMAIL_REGEXP = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/; -var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; - -var inputType = { - - /** - * @ngdoc inputType - * @name ng.directive:input.text - * - * @description - * Standard HTML text input with angular data binding. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Adds `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Single word: - - Required! - - Single word only! - - text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - it('should initialize to model', function() { - expect(binding('text')).toEqual('guest'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - input('text').enter(''); - expect(binding('text')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - - it('should be invalid if multi word', function() { - input('text').enter('hello world'); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - -
- */ - 'text': textInputType, - - - /** - * @ngdoc inputType - * @name ng.directive:input.number - * - * @description - * Text input with number validation and transformation. Sets the `number` validation - * error if not a valid number. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. - * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Number: - - Required! - - Not valid number! - value = {{value}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - it('should initialize to model', function() { - expect(binding('value')).toEqual('12'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - input('value').enter(''); - expect(binding('value')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - - it('should be invalid if over max', function() { - input('value').enter('123'); - expect(binding('value')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - -
- */ - 'number': numberInputType, - - - /** - * @ngdoc inputType - * @name ng.directive:input.url - * - * @description - * Text input with URL validation. Sets the `url` validation error key if the content is not a - * valid URL. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- URL: - - Required! - - Not valid url! - text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.url = {{!!myForm.$error.url}}
-
-
- - it('should initialize to model', function() { - expect(binding('text')).toEqual('http://google.com'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - input('text').enter(''); - expect(binding('text')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - - it('should be invalid if not url', function() { - input('text').enter('xxx'); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - -
- */ - 'url': urlInputType, - - - /** - * @ngdoc inputType - * @name ng.directive:input.email - * - * @description - * Text input with email validation. Sets the `email` validation error key if not a valid email - * address. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * - * @example - - - -
- Email: - - Required! - - Not valid email! - text = {{text}}
- myForm.input.$valid = {{myForm.input.$valid}}
- myForm.input.$error = {{myForm.input.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.email = {{!!myForm.$error.email}}
-
-
- - it('should initialize to model', function() { - expect(binding('text')).toEqual('me@example.com'); - expect(binding('myForm.input.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - input('text').enter(''); - expect(binding('text')).toEqual(''); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - - it('should be invalid if not email', function() { - input('text').enter('xxx'); - expect(binding('myForm.input.$valid')).toEqual('false'); - }); - -
- */ - 'email': emailInputType, - - - /** - * @ngdoc inputType - * @name ng.directive:input.radio - * - * @description - * HTML radio button. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string} value The value to which the expression should be set when selected. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Red
- Green
- Blue
- color = {{color}}
-
-
- - it('should change state', function() { - expect(binding('color')).toEqual('blue'); - - input('color').select('red'); - expect(binding('color')).toEqual('red'); - }); - -
- */ - 'radio': radioInputType, - - - /** - * @ngdoc inputType - * @name ng.directive:input.checkbox - * - * @description - * HTML checkbox. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} ngTrueValue The value to which the expression should be set when selected. - * @param {string=} ngFalseValue The value to which the expression should be set when not selected. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
- Value1:
- Value2:
- value1 = {{value1}}
- value2 = {{value2}}
-
-
- - it('should change state', function() { - expect(binding('value1')).toEqual('true'); - expect(binding('value2')).toEqual('YES'); - - input('value1').check(); - input('value2').check(); - expect(binding('value1')).toEqual('false'); - expect(binding('value2')).toEqual('NO'); - }); - -
- */ - 'checkbox': checkboxInputType, - - 'hidden': noop, - 'button': noop, - 'submit': noop, - 'reset': noop -}; - - -function isEmpty(value) { - return isUndefined(value) || value === '' || value === null || value !== value; -} - - -function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { - - var listener = function() { - var value = trim(element.val()); - - if (ctrl.$viewValue !== value) { - scope.$apply(function() { - ctrl.$setViewValue(value); - }); - } - }; - - // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the - // input event on backspace, delete or cut - if ($sniffer.hasEvent('input')) { - element.bind('input', listener); - } else { - var timeout; - - var deferListener = function() { - if (!timeout) { - timeout = $browser.defer(function() { - listener(); - timeout = null; - }); - } - }; - - element.bind('keydown', function(event) { - var key = event.keyCode; - - // ignore - // command modifiers arrows - if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; - - deferListener(); - }); - - // if user paste into input using mouse, we need "change" event to catch it - element.bind('change', listener); - - // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it - if ($sniffer.hasEvent('paste')) { - element.bind('paste cut', deferListener); - } - } - - - ctrl.$render = function() { - element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); - }; - - // pattern validator - var pattern = attr.ngPattern, - patternValidator; - - var validate = function(regexp, value) { - if (isEmpty(value) || regexp.test(value)) { - ctrl.$setValidity('pattern', true); - return value; - } else { - ctrl.$setValidity('pattern', false); - return undefined; - } - }; - - if (pattern) { - if (pattern.match(/^\/(.*)\/$/)) { - pattern = new RegExp(pattern.substr(1, pattern.length - 2)); - patternValidator = function(value) { - return validate(pattern, value) - }; - } else { - patternValidator = function(value) { - var patternObj = scope.$eval(pattern); - - if (!patternObj || !patternObj.test) { - throw new Error('Expected ' + pattern + ' to be a RegExp but was ' + patternObj); - } - return validate(patternObj, value); - }; - } - - ctrl.$formatters.push(patternValidator); - ctrl.$parsers.push(patternValidator); - } - - // min length validator - if (attr.ngMinlength) { - var minlength = int(attr.ngMinlength); - var minLengthValidator = function(value) { - if (!isEmpty(value) && value.length < minlength) { - ctrl.$setValidity('minlength', false); - return undefined; - } else { - ctrl.$setValidity('minlength', true); - return value; - } - }; - - ctrl.$parsers.push(minLengthValidator); - ctrl.$formatters.push(minLengthValidator); - } - - // max length validator - if (attr.ngMaxlength) { - var maxlength = int(attr.ngMaxlength); - var maxLengthValidator = function(value) { - if (!isEmpty(value) && value.length > maxlength) { - ctrl.$setValidity('maxlength', false); - return undefined; - } else { - ctrl.$setValidity('maxlength', true); - return value; - } - }; - - ctrl.$parsers.push(maxLengthValidator); - ctrl.$formatters.push(maxLengthValidator); - } -} - -function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - ctrl.$parsers.push(function(value) { - var empty = isEmpty(value); - if (empty || NUMBER_REGEXP.test(value)) { - ctrl.$setValidity('number', true); - return value === '' ? null : (empty ? value : parseFloat(value)); - } else { - ctrl.$setValidity('number', false); - return undefined; - } - }); - - ctrl.$formatters.push(function(value) { - return isEmpty(value) ? '' : '' + value; - }); - - if (attr.min) { - var min = parseFloat(attr.min); - var minValidator = function(value) { - if (!isEmpty(value) && value < min) { - ctrl.$setValidity('min', false); - return undefined; - } else { - ctrl.$setValidity('min', true); - return value; - } - }; - - ctrl.$parsers.push(minValidator); - ctrl.$formatters.push(minValidator); - } - - if (attr.max) { - var max = parseFloat(attr.max); - var maxValidator = function(value) { - if (!isEmpty(value) && value > max) { - ctrl.$setValidity('max', false); - return undefined; - } else { - ctrl.$setValidity('max', true); - return value; - } - }; - - ctrl.$parsers.push(maxValidator); - ctrl.$formatters.push(maxValidator); - } - - ctrl.$formatters.push(function(value) { - - if (isEmpty(value) || isNumber(value)) { - ctrl.$setValidity('number', true); - return value; - } else { - ctrl.$setValidity('number', false); - return undefined; - } - }); -} - -function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - var urlValidator = function(value) { - if (isEmpty(value) || URL_REGEXP.test(value)) { - ctrl.$setValidity('url', true); - return value; - } else { - ctrl.$setValidity('url', false); - return undefined; - } - }; - - ctrl.$formatters.push(urlValidator); - ctrl.$parsers.push(urlValidator); -} - -function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { - textInputType(scope, element, attr, ctrl, $sniffer, $browser); - - var emailValidator = function(value) { - if (isEmpty(value) || EMAIL_REGEXP.test(value)) { - ctrl.$setValidity('email', true); - return value; - } else { - ctrl.$setValidity('email', false); - return undefined; - } - }; - - ctrl.$formatters.push(emailValidator); - ctrl.$parsers.push(emailValidator); -} - -function radioInputType(scope, element, attr, ctrl) { - // make the name unique, if not defined - if (isUndefined(attr.name)) { - element.attr('name', nextUid()); - } - - element.bind('click', function() { - if (element[0].checked) { - scope.$apply(function() { - ctrl.$setViewValue(attr.value); - }); - } - }); - - ctrl.$render = function() { - var value = attr.value; - element[0].checked = (value == ctrl.$viewValue); - }; - - attr.$observe('value', ctrl.$render); -} - -function checkboxInputType(scope, element, attr, ctrl) { - var trueValue = attr.ngTrueValue, - falseValue = attr.ngFalseValue; - - if (!isString(trueValue)) trueValue = true; - if (!isString(falseValue)) falseValue = false; - - element.bind('click', function() { - scope.$apply(function() { - ctrl.$setViewValue(element[0].checked); - }); - }); - - ctrl.$render = function() { - element[0].checked = ctrl.$viewValue; - }; - - ctrl.$formatters.push(function(value) { - return value === trueValue; - }); - - ctrl.$parsers.push(function(value) { - return value ? trueValue : falseValue; - }); -} - - -/** - * @ngdoc directive - * @name ng.directive:textarea - * @restrict E - * - * @description - * HTML textarea element control with angular data-binding. The data-binding and validation - * properties of this element are exactly the same as those of the - * {@link ng.directive:input input element}. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to - * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of - * `required` when you want to data-bind to the `required` attribute. - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - */ - - -/** - * @ngdoc directive - * @name ng.directive:input - * @restrict E - * - * @description - * HTML input element control with angular data-binding. Input control follows HTML5 input types - * and polyfills the HTML5 validation behavior for older browsers. - * - * @param {string} ngModel Assignable angular expression to data-bind to. - * @param {string=} name Property name of the form under which the control is published. - * @param {string=} required Sets `required` validation error key if the value is not entered. - * @param {boolean=} ngRequired Sets `required` attribute if set to true - * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than - * minlength. - * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than - * maxlength. - * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the - * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for - * patterns defined as scope expressions. - * @param {string=} ngChange Angular expression to be executed when input changes due to user - * interaction with the input element. - * - * @example - - - -
-
- User name: - - Required!
- Last name: - - Too short! - - Too long!
-
-
- user = {{user}}
- myForm.userName.$valid = {{myForm.userName.$valid}}
- myForm.userName.$error = {{myForm.userName.$error}}
- myForm.lastName.$valid = {{myForm.lastName.$valid}}
- myForm.lastName.$error = {{myForm.lastName.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
- myForm.$error.minlength = {{!!myForm.$error.minlength}}
- myForm.$error.maxlength = {{!!myForm.$error.maxlength}}
-
-
- - it('should initialize to model', function() { - expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}'); - expect(binding('myForm.userName.$valid')).toEqual('true'); - expect(binding('myForm.$valid')).toEqual('true'); - }); - - it('should be invalid if empty when required', function() { - input('user.name').enter(''); - expect(binding('user')).toEqual('{"last":"visitor"}'); - expect(binding('myForm.userName.$valid')).toEqual('false'); - expect(binding('myForm.$valid')).toEqual('false'); - }); - - it('should be valid if empty when min length is set', function() { - input('user.last').enter(''); - expect(binding('user')).toEqual('{"name":"guest","last":""}'); - expect(binding('myForm.lastName.$valid')).toEqual('true'); - expect(binding('myForm.$valid')).toEqual('true'); - }); - - it('should be invalid if less than required min length', function() { - input('user.last').enter('xx'); - expect(binding('user')).toEqual('{"name":"guest"}'); - expect(binding('myForm.lastName.$valid')).toEqual('false'); - expect(binding('myForm.lastName.$error')).toMatch(/minlength/); - expect(binding('myForm.$valid')).toEqual('false'); - }); - - it('should be invalid if longer than max length', function() { - input('user.last').enter('some ridiculously long name'); - expect(binding('user')) - .toEqual('{"name":"guest"}'); - expect(binding('myForm.lastName.$valid')).toEqual('false'); - expect(binding('myForm.lastName.$error')).toMatch(/maxlength/); - expect(binding('myForm.$valid')).toEqual('false'); - }); - -
- */ -var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { - return { - restrict: 'E', - require: '?ngModel', - link: function(scope, element, attr, ctrl) { - if (ctrl) { - (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, - $browser); - } - } - }; -}]; - -var VALID_CLASS = 'ng-valid', - INVALID_CLASS = 'ng-invalid', - PRISTINE_CLASS = 'ng-pristine', - DIRTY_CLASS = 'ng-dirty'; - -/** - * @ngdoc object - * @name ng.directive:ngModel.NgModelController - * - * @property {string} $viewValue Actual string value in the view. - * @property {*} $modelValue The value in the model, that the control is bound to. - * @property {Array.} $parsers Whenever the control reads value from the DOM, it executes - * all of these functions to sanitize / convert the value as well as validate. - * - * @property {Array.} $formatters Whenever the model value changes, it executes all of - * these functions to convert the value as well as validate. - * - * @property {Object} $error An bject hash with all errors as keys. - * - * @property {boolean} $pristine True if user has not interacted with the control yet. - * @property {boolean} $dirty True if user has already interacted with the control. - * @property {boolean} $valid True if there is no error. - * @property {boolean} $invalid True if at least one error on the control. - * - * @description - * - * `NgModelController` provides API for the `ng-model` directive. The controller contains - * services for data-binding, validation, CSS update, value formatting and parsing. It - * specifically does not contain any logic which deals with DOM rendering or listening to - * DOM events. The `NgModelController` is meant to be extended by other directives where, the - * directive provides DOM manipulation and the `NgModelController` provides the data-binding. - * - * This example shows how to use `NgModelController` with a custom control to achieve - * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) - * collaborate together to achieve the desired result. - * - * - - [contenteditable] { - border: 1px solid black; - background-color: white; - min-height: 20px; - } - - .ng-invalid { - border: 1px solid red; - } - - - - angular.module('customControl', []). - directive('contenteditable', function() { - return { - restrict: 'A', // only activate on element attribute - require: '?ngModel', // get a hold of NgModelController - link: function(scope, element, attrs, ngModel) { - if(!ngModel) return; // do nothing if no ng-model - - // Specify how UI should be updated - ngModel.$render = function() { - element.html(ngModel.$viewValue || ''); - }; - - // Listen for change events to enable binding - element.bind('blur keyup change', function() { - scope.$apply(read); - }); - read(); // initialize - - // Write data to the model - function read() { - ngModel.$setViewValue(element.html()); - } - } - }; - }); - - -
-
Change me!
- Required! -
- -
-
- - it('should data-bind and become invalid', function() { - var contentEditable = element('[contenteditable]'); - - expect(contentEditable.text()).toEqual('Change me!'); - input('userContent').enter(''); - expect(contentEditable.text()).toEqual(''); - expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); - }); - - *
- * - */ -var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', - function($scope, $exceptionHandler, $attr, $element, $parse) { - this.$viewValue = Number.NaN; - this.$modelValue = Number.NaN; - this.$parsers = []; - this.$formatters = []; - this.$viewChangeListeners = []; - this.$pristine = true; - this.$dirty = false; - this.$valid = true; - this.$invalid = false; - this.$name = $attr.name; - - var ngModelGet = $parse($attr.ngModel), - ngModelSet = ngModelGet.assign; - - if (!ngModelSet) { - throw Error(NON_ASSIGNABLE_MODEL_EXPRESSION + $attr.ngModel + - ' (' + startingTag($element) + ')'); - } - - /** - * @ngdoc function - * @name ng.directive:ngModel.NgModelController#$render - * @methodOf ng.directive:ngModel.NgModelController - * - * @description - * Called when the view needs to be updated. It is expected that the user of the ng-model - * directive will implement this method. - */ - this.$render = noop; - - var parentForm = $element.inheritedData('$formController') || nullFormCtrl, - invalidCount = 0, // used to easily determine if we are valid - $error = this.$error = {}; // keep invalid keys here - - - // Setup initial state of the control - $element.addClass(PRISTINE_CLASS); - toggleValidCss(true); - - // convenience method for easy toggling of classes - function toggleValidCss(isValid, validationErrorKey) { - validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; - $element. - removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). - addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); - } - - /** - * @ngdoc function - * @name ng.directive:ngModel.NgModelController#$setValidity - * @methodOf ng.directive:ngModel.NgModelController - * - * @description - * Change the validity state, and notifies the form when the control changes validity. (i.e. it - * does not notify form if given validator is already marked as invalid). - * - * This method should be called by validators - i.e. the parser or formatter functions. - * - * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign - * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. - * The `validationErrorKey` should be in camelCase and will get converted into dash-case - * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` - * class and can be bound to as `{{someForm.someControl.$error.myError}}` . - * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). - */ - this.$setValidity = function(validationErrorKey, isValid) { - if ($error[validationErrorKey] === !isValid) return; - - if (isValid) { - if ($error[validationErrorKey]) invalidCount--; - if (!invalidCount) { - toggleValidCss(true); - this.$valid = true; - this.$invalid = false; - } - } else { - toggleValidCss(false); - this.$invalid = true; - this.$valid = false; - invalidCount++; - } - - $error[validationErrorKey] = !isValid; - toggleValidCss(isValid, validationErrorKey); - - parentForm.$setValidity(validationErrorKey, isValid, this); - }; - - - /** - * @ngdoc function - * @name ng.directive:ngModel.NgModelController#$setViewValue - * @methodOf ng.directive:ngModel.NgModelController - * - * @description - * Read a value from view. - * - * This method should be called from within a DOM event handler. - * For example {@link ng.directive:input input} or - * {@link ng.directive:select select} directives call it. - * - * It internally calls all `parsers` and if resulted value is valid, updates the model and - * calls all registered change listeners. - * - * @param {string} value Value from the view. - */ - this.$setViewValue = function(value) { - this.$viewValue = value; - - // change to dirty - if (this.$pristine) { - this.$dirty = true; - this.$pristine = false; - $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); - parentForm.$setDirty(); - } - - forEach(this.$parsers, function(fn) { - value = fn(value); - }); - - if (this.$modelValue !== value) { - this.$modelValue = value; - ngModelSet($scope, value); - forEach(this.$viewChangeListeners, function(listener) { - try { - listener(); - } catch(e) { - $exceptionHandler(e); - } - }) - } - }; - - // model -> value - var ctrl = this; - - $scope.$watch(function ngModelWatch() { - var value = ngModelGet($scope); - - // if scope model value and ngModel value are out of sync - if (ctrl.$modelValue !== value) { - - var formatters = ctrl.$formatters, - idx = formatters.length; - - ctrl.$modelValue = value; - while(idx--) { - value = formatters[idx](value); - } - - if (ctrl.$viewValue !== value) { - ctrl.$viewValue = value; - ctrl.$render(); - } - } - }); -}]; - - -/** - * @ngdoc directive - * @name ng.directive:ngModel - * - * @element input - * - * @description - * Is directive that tells Angular to do two-way data binding. It works together with `input`, - * `select`, `textarea`. You can easily write your own directives to use `ngModel` as well. - * - * `ngModel` is responsible for: - * - * - binding the view into the model, which other directives such as `input`, `textarea` or `select` - * require, - * - providing validation behavior (i.e. required, number, email, url), - * - keeping state of the control (valid/invalid, dirty/pristine, validation errors), - * - setting related css class onto the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`), - * - register the control with parent {@link ng.directive:form form}. - * - * For basic examples, how to use `ngModel`, see: - * - * - {@link ng.directive:input input} - * - {@link ng.directive:input.text text} - * - {@link ng.directive:input.checkbox checkbox} - * - {@link ng.directive:input.radio radio} - * - {@link ng.directive:input.number number} - * - {@link ng.directive:input.email email} - * - {@link ng.directive:input.url url} - * - {@link ng.directive:select select} - * - {@link ng.directive:textarea textarea} - * - */ -var ngModelDirective = function() { - return { - require: ['ngModel', '^?form'], - controller: NgModelController, - link: function(scope, element, attr, ctrls) { - // notify others, especially parent forms - - var modelCtrl = ctrls[0], - formCtrl = ctrls[1] || nullFormCtrl; - - formCtrl.$addControl(modelCtrl); - - element.bind('$destroy', function() { - formCtrl.$removeControl(modelCtrl); - }); - } - }; -}; - - -/** - * @ngdoc directive - * @name ng.directive:ngChange - * @restrict E - * - * @description - * Evaluate given expression when user changes the input. - * The expression is not evaluated when the value change is coming from the model. - * - * Note, this directive requires `ngModel` to be present. - * - * @element input - * - * @example - * - * - * - *
- * - * - *
- * debug = {{confirmed}}
- * counter = {{counter}} - *
- *
- * - * it('should evaluate the expression if changing from view', function() { - * expect(binding('counter')).toEqual('0'); - * element('#ng-change-example1').click(); - * expect(binding('counter')).toEqual('1'); - * expect(binding('confirmed')).toEqual('true'); - * }); - * - * it('should not evaluate the expression if changing from model', function() { - * element('#ng-change-example2').click(); - * expect(binding('counter')).toEqual('0'); - * expect(binding('confirmed')).toEqual('true'); - * }); - * - *
- */ -var ngChangeDirective = valueFn({ - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - ctrl.$viewChangeListeners.push(function() { - scope.$eval(attr.ngChange); - }); - } -}); - - -var requiredDirective = function() { - return { - require: '?ngModel', - link: function(scope, elm, attr, ctrl) { - if (!ctrl) return; - attr.required = true; // force truthy in case we are on non input element - - var validator = function(value) { - if (attr.required && (isEmpty(value) || value === false)) { - ctrl.$setValidity('required', false); - return; - } else { - ctrl.$setValidity('required', true); - return value; - } - }; - - ctrl.$formatters.push(validator); - ctrl.$parsers.unshift(validator); - - attr.$observe('required', function() { - validator(ctrl.$viewValue); - }); - } - }; -}; - - -/** - * @ngdoc directive - * @name ng.directive:ngList - * - * @description - * Text input that converts between comma-separated string into an array of strings. - * - * @element input - * @param {string=} ngList optional delimiter that should be used to split the value. If - * specified in form `/something/` then the value will be converted into a regular expression. - * - * @example - - - -
- List: - - Required! - names = {{names}}
- myForm.namesInput.$valid = {{myForm.namesInput.$valid}}
- myForm.namesInput.$error = {{myForm.namesInput.$error}}
- myForm.$valid = {{myForm.$valid}}
- myForm.$error.required = {{!!myForm.$error.required}}
-
-
- - it('should initialize to model', function() { - expect(binding('names')).toEqual('["igor","misko","vojta"]'); - expect(binding('myForm.namesInput.$valid')).toEqual('true'); - }); - - it('should be invalid if empty', function() { - input('names').enter(''); - expect(binding('names')).toEqual('[]'); - expect(binding('myForm.namesInput.$valid')).toEqual('false'); - }); - -
- */ -var ngListDirective = function() { - return { - require: 'ngModel', - link: function(scope, element, attr, ctrl) { - var match = /\/(.*)\//.exec(attr.ngList), - separator = match && new RegExp(match[1]) || attr.ngList || ','; - - var parse = function(viewValue) { - var list = []; - - if (viewValue) { - forEach(viewValue.split(separator), function(value) { - if (value) list.push(trim(value)); - }); - } - - return list; - }; - - ctrl.$parsers.push(parse); - ctrl.$formatters.push(function(value) { - if (isArray(value)) { - return value.join(', '); - } - - return undefined; - }); - } - }; -}; - - -var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; - -var ngValueDirective = function() { - return { - priority: 100, - compile: function(tpl, tplAttr) { - if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { - return function(scope, elm, attr) { - attr.$set('value', scope.$eval(attr.ngValue)); - }; - } else { - return function(scope, elm, attr) { - scope.$watch(attr.ngValue, function valueWatchAction(value) { - attr.$set('value', value, false); - }); - }; - } - } - }; -}; - -/** - * @ngdoc directive - * @name ng.directive:ngBind - * - * @description - * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element - * with the value of a given expression, and to update the text content when the value of that - * expression changes. - * - * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like - * `{{ expression }}` which is similar but less verbose. - * - * One scenario in which the use of `ngBind` is preferred over `{{ expression }}` binding is when - * it's desirable to put bindings into template that is momentarily displayed by the browser in its - * raw state before Angular compiles it. Since `ngBind` is an element attribute, it makes the - * bindings invisible to the user while the page is loading. - * - * An alternative solution to this problem would be using the - * {@link ng.directive:ngCloak ngCloak} directive. - * - * - * @element ANY - * @param {expression} ngBind {@link guide/expression Expression} to evaluate. - * - * @example - * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. - - - -
- Enter name:
- Hello ! -
-
- - it('should check ng-bind', function() { - expect(using('.doc-example-live').binding('name')).toBe('Whirled'); - using('.doc-example-live').input('name').enter('world'); - expect(using('.doc-example-live').binding('name')).toBe('world'); - }); - -
- */ -var ngBindDirective = ngDirective(function(scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.ngBind); - scope.$watch(attr.ngBind, function ngBindWatchAction(value) { - element.text(value == undefined ? '' : value); - }); -}); - - -/** - * @ngdoc directive - * @name ng.directive:ngBindTemplate - * - * @description - * The `ngBindTemplate` directive specifies that the element - * text should be replaced with the template in ngBindTemplate. - * Unlike ngBind the ngBindTemplate can contain multiple `{{` `}}` - * expressions. (This is required since some HTML elements - * can not have SPAN elements such as TITLE, or OPTION to name a few.) - * - * @element ANY - * @param {string} ngBindTemplate template of form - * {{ expression }} to eval. - * - * @example - * Try it here: enter text in text box and watch the greeting change. - - - -
- Salutation:
- Name:
-

-       
-
- - it('should check ng-bind', function() { - expect(using('.doc-example-live').binding('salutation')). - toBe('Hello'); - expect(using('.doc-example-live').binding('name')). - toBe('World'); - using('.doc-example-live').input('salutation').enter('Greetings'); - using('.doc-example-live').input('name').enter('user'); - expect(using('.doc-example-live').binding('salutation')). - toBe('Greetings'); - expect(using('.doc-example-live').binding('name')). - toBe('user'); - }); - -
- */ -var ngBindTemplateDirective = ['$interpolate', function($interpolate) { - return function(scope, element, attr) { - // TODO: move this to scenario runner - var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); - element.addClass('ng-binding').data('$binding', interpolateFn); - attr.$observe('ngBindTemplate', function(value) { - element.text(value); - }); - } -}]; - - -/** - * @ngdoc directive - * @name ng.directive:ngBindHtmlUnsafe - * - * @description - * Creates a binding that will innerHTML the result of evaluating the `expression` into the current - * element. *The innerHTML-ed content will not be sanitized!* You should use this directive only if - * {@link ngSanitize.directive:ngBindHtml ngBindHtml} directive is too - * restrictive and when you absolutely trust the source of the content you are binding to. - * - * See {@link ngSanitize.$sanitize $sanitize} docs for examples. - * - * @element ANY - * @param {expression} ngBindHtmlUnsafe {@link guide/expression Expression} to evaluate. - */ -var ngBindHtmlUnsafeDirective = [function() { - return function(scope, element, attr) { - element.addClass('ng-binding').data('$binding', attr.ngBindHtmlUnsafe); - scope.$watch(attr.ngBindHtmlUnsafe, function ngBindHtmlUnsafeWatchAction(value) { - element.html(value || ''); - }); - }; -}]; - -function classDirective(name, selector) { - name = 'ngClass' + name; - return ngDirective(function(scope, element, attr) { - var oldVal = undefined; - - scope.$watch(attr[name], ngClassWatchAction, true); - - attr.$observe('class', function(value) { - var ngClass = scope.$eval(attr[name]); - ngClassWatchAction(ngClass, ngClass); - }); - - - if (name !== 'ngClass') { - scope.$watch('$index', function($index, old$index) { - var mod = $index & 1; - if (mod !== old$index & 1) { - if (mod === selector) { - addClass(scope.$eval(attr[name])); - } else { - removeClass(scope.$eval(attr[name])); - } - } - }); - } - - - function ngClassWatchAction(newVal) { - if (selector === true || scope.$index % 2 === selector) { - if (oldVal && !equals(newVal,oldVal)) { - removeClass(oldVal); - } - addClass(newVal); - } - oldVal = copy(newVal); - } - - - function removeClass(classVal) { - if (isObject(classVal) && !isArray(classVal)) { - classVal = map(classVal, function(v, k) { if (v) return k }); - } - element.removeClass(isArray(classVal) ? classVal.join(' ') : classVal); - } - - - function addClass(classVal) { - if (isObject(classVal) && !isArray(classVal)) { - classVal = map(classVal, function(v, k) { if (v) return k }); - } - if (classVal) { - element.addClass(isArray(classVal) ? classVal.join(' ') : classVal); - } - } - }); -} - -/** - * @ngdoc directive - * @name ng.directive:ngClass - * - * @description - * The `ngClass` allows you to set CSS class on HTML element dynamically by databinding an - * expression that represents all classes to be added. - * - * The directive won't add duplicate classes if a particular class was already set. - * - * When the expression changes, the previously added classes are removed and only then the - * new classes are added. - * - * @element ANY - * @param {expression} ngClass {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class - * names, an array, or a map of class names to boolean values. - * - * @example - - - - -
- Sample Text -
- - .my-class { - color: red; - } - - - it('should check ng-class', function() { - expect(element('.doc-example-live span').prop('className')).not(). - toMatch(/my-class/); - - using('.doc-example-live').element(':button:first').click(); - - expect(element('.doc-example-live span').prop('className')). - toMatch(/my-class/); - - using('.doc-example-live').element(':button:last').click(); - - expect(element('.doc-example-live span').prop('className')).not(). - toMatch(/my-class/); - }); - -
- */ -var ngClassDirective = classDirective('', true); - -/** - * @ngdoc directive - * @name ng.directive:ngClassOdd - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except it works in - * conjunction with `ngRepeat` and takes affect only on odd (even) rows. - * - * This directive can be applied only within a scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result - * of the evaluation can be a string representing space delimited class names or an array. - * - * @example - - -
    -
  1. - - {{name}} - -
  2. -
-
- - .odd { - color: red; - } - .even { - color: blue; - } - - - it('should check ng-class-odd and ng-class-even', function() { - expect(element('.doc-example-live li:first span').prop('className')). - toMatch(/odd/); - expect(element('.doc-example-live li:last span').prop('className')). - toMatch(/even/); - }); - -
- */ -var ngClassOddDirective = classDirective('Odd', 0); - -/** - * @ngdoc directive - * @name ng.directive:ngClassEven - * - * @description - * The `ngClassOdd` and `ngClassEven` directives work exactly as - * {@link ng.directive:ngClass ngClass}, except it works in - * conjunction with `ngRepeat` and takes affect only on odd (even) rows. - * - * This directive can be applied only within a scope of an - * {@link ng.directive:ngRepeat ngRepeat}. - * - * @element ANY - * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The - * result of the evaluation can be a string representing space delimited class names or an array. - * - * @example - - -
    -
  1. - - {{name}}       - -
  2. -
-
- - .odd { - color: red; - } - .even { - color: blue; - } - - - it('should check ng-class-odd and ng-class-even', function() { - expect(element('.doc-example-live li:first span').prop('className')). - toMatch(/odd/); - expect(element('.doc-example-live li:last span').prop('className')). - toMatch(/even/); - }); - -
- */ -var ngClassEvenDirective = classDirective('Even', 1); - -/** - * @ngdoc directive - * @name ng.directive:ngCloak - * - * @description - * The `ngCloak` directive is used to prevent the Angular html template from being briefly - * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this - * directive to avoid the undesirable flicker effect caused by the html template display. - * - * The directive can be applied to the `` element, but typically a fine-grained application is - * prefered in order to benefit from progressive rendering of the browser view. - * - * `ngCloak` works in cooperation with a css rule that is embedded within `angular.js` and - * `angular.min.js` files. Following is the css rule: - * - *
- * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
- *   display: none;
- * }
- * 
- * - * When this css rule is loaded by the browser, all html elements (including their children) that - * are tagged with the `ng-cloak` directive are hidden. When Angular comes across this directive - * during the compilation of the template it deletes the `ngCloak` element attribute, which - * makes the compiled element visible. - * - * For the best result, `angular.js` script must be loaded in the head section of the html file; - * alternatively, the css rule (above) must be included in the external stylesheet of the - * application. - * - * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they - * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css - * class `ngCloak` in addition to `ngCloak` directive as shown in the example below. - * - * @element ANY - * - * @example - - -
{{ 'hello' }}
-
{{ 'hello IE7' }}
-
- - it('should remove the template directive and css class', function() { - expect(element('.doc-example-live #template1').attr('ng-cloak')). - not().toBeDefined(); - expect(element('.doc-example-live #template2').attr('ng-cloak')). - not().toBeDefined(); - }); - -
- * - */ -var ngCloakDirective = ngDirective({ - compile: function(element, attr) { - attr.$set('ngCloak', undefined); - element.removeClass('ng-cloak'); - } -}); - -/** - * @ngdoc directive - * @name ng.directive:ngController - * - * @description - * The `ngController` directive assigns behavior to a scope. This is a key aspect of how angular - * supports the principles behind the Model-View-Controller design pattern. - * - * MVC components in angular: - * - * * Model — The Model is data in scope properties; scopes are attached to the DOM. - * * View — The template (HTML with data bindings) is rendered into the View. - * * Controller — The `ngController` directive specifies a Controller class; the class has - * methods that typically express the business logic behind the application. - * - * Note that an alternative way to define controllers is via the {@link ng.$route $route} service. - * - * @element ANY - * @scope - * @param {expression} ngController Name of a globally accessible constructor function or an - * {@link guide/expression expression} that on the current scope evaluates to a - * constructor function. - * - * @example - * Here is a simple form for editing user contact information. Adding, removing, clearing, and - * greeting are methods declared on the controller (see source tab). These methods can - * easily be called from the angular markup. Notice that the scope becomes the `this` for the - * controller's instance. This allows for easy access to the view data from the controller. Also - * notice that any changes to the data are automatically reflected in the View without the need - * for a manual update. - - - -
- Name: - [ greet ]
- Contact: -
    -
  • - - - [ clear - | X ] -
  • -
  • [ add ]
  • -
-
-
- - it('should check controller', function() { - expect(element('.doc-example-live div>:input').val()).toBe('John Smith'); - expect(element('.doc-example-live li:nth-child(1) input').val()) - .toBe('408 555 1212'); - expect(element('.doc-example-live li:nth-child(2) input').val()) - .toBe('john.smith@example.org'); - - element('.doc-example-live li:first a:contains("clear")').click(); - expect(element('.doc-example-live li:first input').val()).toBe(''); - - element('.doc-example-live li:last a:contains("add")').click(); - expect(element('.doc-example-live li:nth-child(3) input').val()) - .toBe('yourname@example.org'); - }); - -
- */ -var ngControllerDirective = [function() { - return { - scope: true, - controller: '@' - }; -}]; - -/** - * @ngdoc directive - * @name ng.directive:ngCsp - * @priority 1000 - * - * @element html - * @description - * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. - * - * This is necessary when developing things like Google Chrome Extensions. - * - * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). - * For us to be compatible, we just need to implement the "getterFn" in $parse without violating - * any of these restrictions. - * - * AngularJS uses `Function(string)` generated functions as a speed optimization. By applying `ngCsp` - * it is be possible to opt into the CSP compatible mode. When this mode is on AngularJS will - * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will - * be raised. - * - * In order to use this feature put `ngCsp` directive on the root element of the application. - * - * @example - * This example shows how to apply the `ngCsp` directive to the `html` tag. -
-     
-     
-     ...
-     ...
-     
-   
- */ - -var ngCspDirective = ['$sniffer', function($sniffer) { - return { - priority: 1000, - compile: function() { - $sniffer.csp = true; - } - }; -}]; - -/** - * @ngdoc directive - * @name ng.directive:ngClick - * - * @description - * The ngClick allows you to specify custom behavior when - * element is clicked. - * - * @element ANY - * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon - * click. (Event object is available as `$event`) - * - * @example - - - - count: {{count}} - - - it('should check ng-click', function() { - expect(binding('count')).toBe('0'); - element('.doc-example-live :button').click(); - expect(binding('count')).toBe('1'); - }); - - - */ -/* - * A directive that allows creation of custom onclick handlers that are defined as angular - * expressions and are compiled and executed within the current scope. - * - * Events that are handled via these handler are always configured not to propagate further. - */ -var ngEventDirectives = {}; -forEach( - 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave'.split(' '), - function(name) { - var directiveName = directiveNormalize('ng-' + name); - ngEventDirectives[directiveName] = ['$parse', function($parse) { - return function(scope, element, attr) { - var fn = $parse(attr[directiveName]); - element.bind(lowercase(name), function(event) { - scope.$apply(function() { - fn(scope, {$event:event}); - }); - }); - }; - }]; - } -); - -/** - * @ngdoc directive - * @name ng.directive:ngDblclick - * - * @description - * The `ngDblclick` directive allows you to specify custom behavior on dblclick event. - * - * @element ANY - * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon - * dblclick. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngMousedown - * - * @description - * The ngMousedown directive allows you to specify custom behavior on mousedown event. - * - * @element ANY - * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon - * mousedown. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngMouseup - * - * @description - * Specify custom behavior on mouseup event. - * - * @element ANY - * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon - * mouseup. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - -/** - * @ngdoc directive - * @name ng.directive:ngMouseover - * - * @description - * Specify custom behavior on mouseover event. - * - * @element ANY - * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon - * mouseover. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngMouseenter - * - * @description - * Specify custom behavior on mouseenter event. - * - * @element ANY - * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon - * mouseenter. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngMouseleave - * - * @description - * Specify custom behavior on mouseleave event. - * - * @element ANY - * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon - * mouseleave. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngMousemove - * - * @description - * Specify custom behavior on mousemove event. - * - * @element ANY - * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon - * mousemove. (Event object is available as `$event`) - * - * @example - * See {@link ng.directive:ngClick ngClick} - */ - - -/** - * @ngdoc directive - * @name ng.directive:ngSubmit - * - * @description - * Enables binding angular expressions to onsubmit events. - * - * Additionally it prevents the default action (which for form means sending the request to the - * server and reloading the current page). - * - * @element form - * @param {expression} ngSubmit {@link guide/expression Expression} to eval. - * - * @example - - - -
- Enter text and hit enter: - - -
list={{list}}
-
-
- - it('should check ng-submit', function() { - expect(binding('list')).toBe('[]'); - element('.doc-example-live #submit').click(); - expect(binding('list')).toBe('["hello"]'); - expect(input('text').val()).toBe(''); - }); - it('should ignore empty strings', function() { - expect(binding('list')).toBe('[]'); - element('.doc-example-live #submit').click(); - element('.doc-example-live #submit').click(); - expect(binding('list')).toBe('["hello"]'); - }); - -
- */ -var ngSubmitDirective = ngDirective(function(scope, element, attrs) { - element.bind('submit', function() { - scope.$apply(attrs.ngSubmit); - }); -}); - -/** - * @ngdoc directive - * @name ng.directive:ngInclude - * @restrict ECA - * - * @description - * Fetches, compiles and includes an external HTML fragment. - * - * Keep in mind that Same Origin Policy applies to included resources - * (e.g. ngInclude won't work for cross-domain requests on all browsers and for - * file:// access on some browsers). - * - * @scope - * - * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, - * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. - * @param {string=} onload Expression to evaluate when a new partial is loaded. - * - * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll - * $anchorScroll} to scroll the viewport after the content is loaded. - * - * - If the attribute is not set, disable scrolling. - * - If the attribute is set without value, enable scrolling. - * - Otherwise enable scrolling only if the expression evaluates to truthy value. - * - * @example - - -
- - url of the template: {{template.url}} -
-
-
-
- - function Ctrl($scope) { - $scope.templates = - [ { name: 'template1.html', url: 'template1.html'} - , { name: 'template2.html', url: 'template2.html'} ]; - $scope.template = $scope.templates[0]; - } - - - Content of template1.html - - - Content of template2.html - - - it('should load template1.html', function() { - expect(element('.doc-example-live [ng-include]').text()). - toMatch(/Content of template1.html/); - }); - it('should load template2.html', function() { - select('template').option('1'); - expect(element('.doc-example-live [ng-include]').text()). - toMatch(/Content of template2.html/); - }); - it('should change to blank', function() { - select('template').option(''); - expect(element('.doc-example-live [ng-include]').text()).toEqual(''); - }); - -
- */ - - -/** - * @ngdoc event - * @name ng.directive:ngInclude#$includeContentLoaded - * @eventOf ng.directive:ngInclude - * @eventType emit on the current ngInclude scope - * @description - * Emitted every time the ngInclude content is reloaded. - */ -var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$compile', - function($http, $templateCache, $anchorScroll, $compile) { - return { - restrict: 'ECA', - terminal: true, - compile: function(element, attr) { - var srcExp = attr.ngInclude || attr.src, - onloadExp = attr.onload || '', - autoScrollExp = attr.autoscroll; - - return function(scope, element) { - var changeCounter = 0, - childScope; - - var clearContent = function() { - if (childScope) { - childScope.$destroy(); - childScope = null; - } - - element.html(''); - }; - - scope.$watch(srcExp, function ngIncludeWatchAction(src) { - var thisChangeId = ++changeCounter; - - if (src) { - $http.get(src, {cache: $templateCache}).success(function(response) { - if (thisChangeId !== changeCounter) return; - - if (childScope) childScope.$destroy(); - childScope = scope.$new(); - - element.html(response); - $compile(element.contents())(childScope); - - if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { - $anchorScroll(); - } - - childScope.$emit('$includeContentLoaded'); - scope.$eval(onloadExp); - }).error(function() { - if (thisChangeId === changeCounter) clearContent(); - }); - } else clearContent(); - }); - }; - } - }; -}]; - -/** - * @ngdoc directive - * @name ng.directive:ngInit - * - * @description - * The `ngInit` directive specifies initialization tasks to be executed - * before the template enters execution mode during bootstrap. - * - * @element ANY - * @param {expression} ngInit {@link guide/expression Expression} to eval. - * - * @example - - -
- {{greeting}} {{person}}! -
-
- - it('should check greeting', function() { - expect(binding('greeting')).toBe('Hello'); - expect(binding('person')).toBe('World'); - }); - -
- */ -var ngInitDirective = ngDirective({ - compile: function() { - return { - pre: function(scope, element, attrs) { - scope.$eval(attrs.ngInit); - } - } - } -}); - -/** - * @ngdoc directive - * @name ng.directive:ngNonBindable - * @priority 1000 - * - * @description - * Sometimes it is necessary to write code which looks like bindings but which should be left alone - * by angular. Use `ngNonBindable` to make angular ignore a chunk of HTML. - * - * @element ANY - * - * @example - * In this example there are two location where a simple binding (`{{}}`) is present, but the one - * wrapped in `ngNonBindable` is left alone. - * - * @example - - -
Normal: {{1 + 2}}
-
Ignored: {{1 + 2}}
-
- - it('should check ng-non-bindable', function() { - expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); - expect(using('.doc-example-live').element('div:last').text()). - toMatch(/1 \+ 2/); - }); - -
- */ -var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); - -/** - * @ngdoc directive - * @name ng.directive:ngPluralize - * @restrict EA - * - * @description - * # Overview - * `ngPluralize` is a directive that displays messages according to en-US localization rules. - * These rules are bundled with angular.js and the rules can be overridden - * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive - * by specifying the mappings between - * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - * plural categories} and the strings to be displayed. - * - * # Plural categories and explicit number rules - * There are two - * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html - * plural categories} in Angular's default en-US locale: "one" and "other". - * - * While a pural category may match many numbers (for example, in en-US locale, "other" can match - * any number that is not 1), an explicit number rule can only match one number. For example, the - * explicit number rule for "3" matches the number 3. You will see the use of plural categories - * and explicit number rules throughout later parts of this documentation. - * - * # Configuring ngPluralize - * You configure ngPluralize by providing 2 attributes: `count` and `when`. - * You can also provide an optional attribute, `offset`. - * - * The value of the `count` attribute can be either a string or an {@link guide/expression - * Angular expression}; these are evaluated on the current scope for its bound value. - * - * The `when` attribute specifies the mappings between plural categories and the actual - * string to be displayed. The value of the attribute should be a JSON object so that Angular - * can interpret it correctly. - * - * The following example shows how to configure ngPluralize: - * - *
- * 
- * 
- *
- * - * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not - * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" - * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for - * other numbers, for example 12, so that instead of showing "12 people are viewing", you can - * show "a dozen people are viewing". - * - * You can use a set of closed braces(`{}`) as a placeholder for the number that you want substituted - * into pluralized strings. In the previous example, Angular will replace `{}` with - * `{{personCount}}`. The closed braces `{}` is a placeholder - * for {{numberExpression}}. - * - * # Configuring ngPluralize with offset - * The `offset` attribute allows further customization of pluralized text, which can result in - * a better user experience. For example, instead of the message "4 people are viewing this document", - * you might display "John, Kate and 2 others are viewing this document". - * The offset attribute allows you to offset a number by any desired value. - * Let's take a look at an example: - * - *
- * 
- * 
- * 
- * - * Notice that we are still using two plural categories(one, other), but we added - * three explicit number rules 0, 1 and 2. - * When one person, perhaps John, views the document, "John is viewing" will be shown. - * When three people view the document, no explicit number rule is found, so - * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. - * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" - * is shown. - * - * Note that when you specify offsets, you must provide explicit number rules for - * numbers from 0 up to and including the offset. If you use an offset of 3, for example, - * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for - * plural categories "one" and "other". - * - * @param {string|expression} count The variable to be bounded to. - * @param {string} when The mapping between plural category to its correspoding strings. - * @param {number=} offset Offset to deduct from the total number. - * - * @example - - - -
- Person 1:
- Person 2:
- Number of People:
- - - Without Offset: - -
- - - With Offset(2): - - -
-
- - it('should show correct pluralized string', function() { - expect(element('.doc-example-live ng-pluralize:first').text()). - toBe('1 person is viewing.'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Igor is viewing.'); - - using('.doc-example-live').input('personCount').enter('0'); - expect(element('.doc-example-live ng-pluralize:first').text()). - toBe('Nobody is viewing.'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Nobody is viewing.'); - - using('.doc-example-live').input('personCount').enter('2'); - expect(element('.doc-example-live ng-pluralize:first').text()). - toBe('2 people are viewing.'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Igor and Misko are viewing.'); - - using('.doc-example-live').input('personCount').enter('3'); - expect(element('.doc-example-live ng-pluralize:first').text()). - toBe('3 people are viewing.'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Igor, Misko and one other person are viewing.'); - - using('.doc-example-live').input('personCount').enter('4'); - expect(element('.doc-example-live ng-pluralize:first').text()). - toBe('4 people are viewing.'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Igor, Misko and 2 other people are viewing.'); - }); - - it('should show data-binded names', function() { - using('.doc-example-live').input('personCount').enter('4'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Igor, Misko and 2 other people are viewing.'); - - using('.doc-example-live').input('person1').enter('Di'); - using('.doc-example-live').input('person2').enter('Vojta'); - expect(element('.doc-example-live ng-pluralize:last').text()). - toBe('Di, Vojta and 2 other people are viewing.'); - }); - -
- */ -var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { - var BRACE = /{}/g; - return { - restrict: 'EA', - link: function(scope, element, attr) { - var numberExp = attr.count, - whenExp = element.attr(attr.$attr.when), // this is because we have {{}} in attrs - offset = attr.offset || 0, - whens = scope.$eval(whenExp), - whensExpFns = {}, - startSymbol = $interpolate.startSymbol(), - endSymbol = $interpolate.endSymbol(); - - forEach(whens, function(expression, key) { - whensExpFns[key] = - $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + - offset + endSymbol)); - }); - - scope.$watch(function ngPluralizeWatch() { - var value = parseFloat(scope.$eval(numberExp)); - - if (!isNaN(value)) { - //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, - //check it against pluralization rules in $locale service - if (!(value in whens)) value = $locale.pluralCat(value - offset); - return whensExpFns[value](scope, element, true); - } else { - return ''; - } - }, function ngPluralizeWatchAction(newVal) { - element.text(newVal); - }); - } - }; -}]; - -/** - * @ngdoc directive - * @name ng.directive:ngRepeat - * - * @description - * The `ngRepeat` directive instantiates a template once per item from a collection. Each template - * instance gets its own scope, where the given loop variable is set to the current collection item, - * and `$index` is set to the item index or key. - * - * Special properties are exposed on the local scope of each template instance, including: - * - * * `$index` – `{number}` – iterator offset of the repeated element (0..length-1) - * * `$first` – `{boolean}` – true if the repeated element is first in the iterator. - * * `$middle` – `{boolean}` – true if the repeated element is between the first and last in the iterator. - * * `$last` – `{boolean}` – true if the repeated element is last in the iterator. - * - * - * @element ANY - * @scope - * @priority 1000 - * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. Two - * formats are currently supported: - * - * * `variable in expression` – where variable is the user defined loop variable and `expression` - * is a scope expression giving the collection to enumerate. - * - * For example: `track in cd.tracks`. - * - * * `(key, value) in expression` – where `key` and `value` can be any user defined identifiers, - * and `expression` is the scope expression giving the collection to enumerate. - * - * For example: `(name, age) in {'adam':10, 'amalie':12}`. - * - * @example - * This example initializes the scope to a list of names and - * then uses `ngRepeat` to display every person: - - -
- I have {{friends.length}} friends. They are: -
    -
  • - [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. -
  • -
-
-
- - it('should check ng-repeat', function() { - var r = using('.doc-example-live').repeater('ul li'); - expect(r.count()).toBe(2); - expect(r.row(0)).toEqual(["1","John","25"]); - expect(r.row(1)).toEqual(["2","Mary","28"]); - }); - -
- */ -var ngRepeatDirective = ngDirective({ - transclude: 'element', - priority: 1000, - terminal: true, - compile: function(element, attr, linker) { - return function(scope, iterStartElement, attr){ - var expression = attr.ngRepeat; - var match = expression.match(/^\s*(.+)\s+in\s+(.*)\s*$/), - lhs, rhs, valueIdent, keyIdent; - if (! match) { - throw Error("Expected ngRepeat in form of '_item_ in _collection_' but got '" + - expression + "'."); - } - lhs = match[1]; - rhs = match[2]; - match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); - if (!match) { - throw Error("'item' in 'item in collection' should be identifier or (key, value) but got '" + - lhs + "'."); - } - valueIdent = match[3] || match[1]; - keyIdent = match[2]; - - // Store a list of elements from previous run. This is a hash where key is the item from the - // iterator, and the value is an array of objects with following properties. - // - scope: bound scope - // - element: previous element. - // - index: position - // We need an array of these objects since the same object can be returned from the iterator. - // We expect this to be a rare case. - var lastOrder = new HashQueueMap(); - - scope.$watch(function ngRepeatWatch(scope){ - var index, length, - collection = scope.$eval(rhs), - cursor = iterStartElement, // current position of the node - // Same as lastOrder but it has the current state. It will become the - // lastOrder on the next iteration. - nextOrder = new HashQueueMap(), - arrayBound, - childScope, - key, value, // key/value of iteration - array, - last; // last object information {scope, element, index} - - - - if (!isArray(collection)) { - // if object, extract keys, sort them and use to determine order of iteration over obj props - array = []; - for(key in collection) { - if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { - array.push(key); - } - } - array.sort(); - } else { - array = collection || []; - } - - arrayBound = array.length-1; - - // we are not using forEach for perf reasons (trying to avoid #call) - for (index = 0, length = array.length; index < length; index++) { - key = (collection === array) ? index : array[index]; - value = collection[key]; - - last = lastOrder.shift(value); - - if (last) { - // if we have already seen this object, then we need to reuse the - // associated scope/element - childScope = last.scope; - nextOrder.push(value, last); - - if (index === last.index) { - // do nothing - cursor = last.element; - } else { - // existing item which got moved - last.index = index; - // This may be a noop, if the element is next, but I don't know of a good way to - // figure this out, since it would require extra DOM access, so let's just hope that - // the browsers realizes that it is noop, and treats it as such. - cursor.after(last.element); - cursor = last.element; - } - } else { - // new item which we don't know about - childScope = scope.$new(); - } - - childScope[valueIdent] = value; - if (keyIdent) childScope[keyIdent] = key; - childScope.$index = index; - - childScope.$first = (index === 0); - childScope.$last = (index === arrayBound); - childScope.$middle = !(childScope.$first || childScope.$last); - - if (!last) { - linker(childScope, function(clone){ - cursor.after(clone); - last = { - scope: childScope, - element: (cursor = clone), - index: index - }; - nextOrder.push(value, last); - }); - } - } - - //shrink children - for (key in lastOrder) { - if (lastOrder.hasOwnProperty(key)) { - array = lastOrder[key]; - while(array.length) { - value = array.pop(); - value.element.remove(); - value.scope.$destroy(); - } - } - } - - lastOrder = nextOrder; - }); - }; - } -}); - -/** - * @ngdoc directive - * @name ng.directive:ngShow - * - * @description - * The `ngShow` and `ngHide` directives show or hide a portion of the DOM tree (HTML) - * conditionally. - * - * @element ANY - * @param {expression} ngShow If the {@link guide/expression expression} is truthy - * then the element is shown or hidden respectively. - * - * @example - - - Click me:
- Show: I show up when your checkbox is checked.
- Hide: I hide when your checkbox is checked. -
- - it('should check ng-show / ng-hide', function() { - expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); - expect(element('.doc-example-live span:last:visible').count()).toEqual(1); - - input('checked').check(); - - expect(element('.doc-example-live span:first:visible').count()).toEqual(1); - expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); - }); - -
- */ -//TODO(misko): refactor to remove element from the DOM -var ngShowDirective = ngDirective(function(scope, element, attr){ - scope.$watch(attr.ngShow, function ngShowWatchAction(value){ - element.css('display', toBoolean(value) ? '' : 'none'); - }); -}); - - -/** - * @ngdoc directive - * @name ng.directive:ngHide - * - * @description - * The `ngHide` and `ngShow` directives hide or show a portion of the DOM tree (HTML) - * conditionally. - * - * @element ANY - * @param {expression} ngHide If the {@link guide/expression expression} is truthy then - * the element is shown or hidden respectively. - * - * @example - - - Click me:
- Show: I show up when you checkbox is checked?
- Hide: I hide when you checkbox is checked? -
- - it('should check ng-show / ng-hide', function() { - expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); - expect(element('.doc-example-live span:last:visible').count()).toEqual(1); - - input('checked').check(); - - expect(element('.doc-example-live span:first:visible').count()).toEqual(1); - expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); - }); - -
- */ -//TODO(misko): refactor to remove element from the DOM -var ngHideDirective = ngDirective(function(scope, element, attr){ - scope.$watch(attr.ngHide, function ngHideWatchAction(value){ - element.css('display', toBoolean(value) ? 'none' : ''); - }); -}); - -/** - * @ngdoc directive - * @name ng.directive:ngStyle - * - * @description - * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. - * - * @element ANY - * @param {expression} ngStyle {@link guide/expression Expression} which evals to an - * object whose keys are CSS style names and values are corresponding values for those CSS - * keys. - * - * @example - - - - -
- Sample Text -
myStyle={{myStyle}}
-
- - span { - color: black; - } - - - it('should check ng-style', function() { - expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); - element('.doc-example-live :button[value=set]').click(); - expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); - element('.doc-example-live :button[value=clear]').click(); - expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); - }); - -
- */ -var ngStyleDirective = ngDirective(function(scope, element, attr) { - scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { - if (oldStyles && (newStyles !== oldStyles)) { - forEach(oldStyles, function(val, style) { element.css(style, '');}); - } - if (newStyles) element.css(newStyles); - }, true); -}); - -/** - * @ngdoc directive - * @name ng.directive:ngSwitch - * @restrict EA - * - * @description - * Conditionally change the DOM structure. - * - * @usage - * - * ... - * ... - * ... - * ... - * - * - * @scope - * @param {*} ngSwitch|on expression to match against ng-switch-when. - * @paramDescription - * On child elments add: - * - * * `ngSwitchWhen`: the case statement to match against. If match then this - * case will be displayed. - * * `ngSwitchDefault`: the default case when no other casses match. - * - * @example - - - -
- - selection={{selection}} -
-
-
Settings Div
- Home Span - default -
-
-
- - it('should start in settings', function() { - expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/); - }); - it('should change to home', function() { - select('selection').option('home'); - expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/); - }); - it('should select deafault', function() { - select('selection').option('other'); - expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/); - }); - -
- */ -var NG_SWITCH = 'ng-switch'; -var ngSwitchDirective = valueFn({ - restrict: 'EA', - require: 'ngSwitch', - // asks for $scope to fool the BC controller module - controller: ['$scope', function ngSwitchController() { - this.cases = {}; - }], - link: function(scope, element, attr, ctrl) { - var watchExpr = attr.ngSwitch || attr.on, - selectedTransclude, - selectedElement, - selectedScope; - - scope.$watch(watchExpr, function ngSwitchWatchAction(value) { - if (selectedElement) { - selectedScope.$destroy(); - selectedElement.remove(); - selectedElement = selectedScope = null; - } - if ((selectedTransclude = ctrl.cases['!' + value] || ctrl.cases['?'])) { - scope.$eval(attr.change); - selectedScope = scope.$new(); - selectedTransclude(selectedScope, function(caseElement) { - selectedElement = caseElement; - element.append(caseElement); - }); - } - }); - } -}); - -var ngSwitchWhenDirective = ngDirective({ - transclude: 'element', - priority: 500, - require: '^ngSwitch', - compile: function(element, attrs, transclude) { - return function(scope, element, attr, ctrl) { - ctrl.cases['!' + attrs.ngSwitchWhen] = transclude; - }; - } -}); - -var ngSwitchDefaultDirective = ngDirective({ - transclude: 'element', - priority: 500, - require: '^ngSwitch', - compile: function(element, attrs, transclude) { - return function(scope, element, attr, ctrl) { - ctrl.cases['?'] = transclude; - }; - } -}); - -/** - * @ngdoc directive - * @name ng.directive:ngTransclude - * - * @description - * Insert the transcluded DOM here. - * - * @element ANY - * - * @example - - - -
-
-
- {{text}} -
-
- - it('should have transcluded', function() { - input('title').enter('TITLE'); - input('text').enter('TEXT'); - expect(binding('title')).toEqual('TITLE'); - expect(binding('text')).toEqual('TEXT'); - }); - -
- * - */ -var ngTranscludeDirective = ngDirective({ - controller: ['$transclude', '$element', function($transclude, $element) { - $transclude(function(clone) { - $element.append(clone); - }); - }] -}); - -/** - * @ngdoc directive - * @name ng.directive:ngView - * @restrict ECA - * - * @description - * # Overview - * `ngView` is a directive that complements the {@link ng.$route $route} service by - * including the rendered template of the current route into the main layout (`index.html`) file. - * Every time the current route changes, the included view changes with it according to the - * configuration of the `$route` service. - * - * @scope - * @example - - -
- Choose: - Moby | - Moby: Ch1 | - Gatsby | - Gatsby: Ch4 | - Scarlet Letter
- -
-
- -
$location.path() = {{$location.path()}}
-
$route.current.templateUrl = {{$route.current.templateUrl}}
-
$route.current.params = {{$route.current.params}}
-
$route.current.scope.name = {{$route.current.scope.name}}
-
$routeParams = {{$routeParams}}
-
-
- - - controller: {{name}}
- Book Id: {{params.bookId}}
-
- - - controller: {{name}}
- Book Id: {{params.bookId}}
- Chapter Id: {{params.chapterId}} -
- - - angular.module('ngView', [], function($routeProvider, $locationProvider) { - $routeProvider.when('/Book/:bookId', { - templateUrl: 'book.html', - controller: BookCntl - }); - $routeProvider.when('/Book/:bookId/ch/:chapterId', { - templateUrl: 'chapter.html', - controller: ChapterCntl - }); - - // configure html5 to get links working on jsfiddle - $locationProvider.html5Mode(true); - }); - - function MainCntl($scope, $route, $routeParams, $location) { - $scope.$route = $route; - $scope.$location = $location; - $scope.$routeParams = $routeParams; - } - - function BookCntl($scope, $routeParams) { - $scope.name = "BookCntl"; - $scope.params = $routeParams; - } - - function ChapterCntl($scope, $routeParams) { - $scope.name = "ChapterCntl"; - $scope.params = $routeParams; - } - - - - it('should load and compile correct template', function() { - element('a:contains("Moby: Ch1")').click(); - var content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: ChapterCntl/); - expect(content).toMatch(/Book Id\: Moby/); - expect(content).toMatch(/Chapter Id\: 1/); - - element('a:contains("Scarlet")').click(); - content = element('.doc-example-live [ng-view]').text(); - expect(content).toMatch(/controller\: BookCntl/); - expect(content).toMatch(/Book Id\: Scarlet/); - }); - -
- */ - - -/** - * @ngdoc event - * @name ng.directive:ngView#$viewContentLoaded - * @eventOf ng.directive:ngView - * @eventType emit on the current ngView scope - * @description - * Emitted every time the ngView content is reloaded. - */ -var ngViewDirective = ['$http', '$templateCache', '$route', '$anchorScroll', '$compile', - '$controller', - function($http, $templateCache, $route, $anchorScroll, $compile, - $controller) { - return { - restrict: 'ECA', - terminal: true, - link: function(scope, element, attr) { - var lastScope, - onloadExp = attr.onload || ''; - - scope.$on('$routeChangeSuccess', update); - update(); - - - function destroyLastScope() { - if (lastScope) { - lastScope.$destroy(); - lastScope = null; - } - } - - function clearContent() { - element.html(''); - destroyLastScope(); - } - - function update() { - var locals = $route.current && $route.current.locals, - template = locals && locals.$template; - - if (template) { - element.html(template); - destroyLastScope(); - - var link = $compile(element.contents()), - current = $route.current, - controller; - - lastScope = current.scope = scope.$new(); - if (current.controller) { - locals.$scope = lastScope; - controller = $controller(current.controller, locals); - element.children().data('$ngControllerController', controller); - } - - link(lastScope); - lastScope.$emit('$viewContentLoaded'); - lastScope.$eval(onloadExp); - - // $anchorScroll might listen on event... - $anchorScroll(); - } else { - clearContent(); - } - } - } - }; -}]; - -/** - * @ngdoc directive - * @name ng.directive:script - * - * @description - * Load content of a script tag, with type `text/ng-template`, into `$templateCache`, so that the - * template can be used by `ngInclude`, `ngView` or directive templates. - * - * @restrict E - * @param {'text/ng-template'} type must be set to `'text/ng-template'` - * - * @example - - - - - Load inlined template -
-
- - it('should load template defined inside script tag', function() { - element('#tpl-link').click(); - expect(element('#tpl-content').text()).toMatch(/Content of the template/); - }); - -
- */ -var scriptDirective = ['$templateCache', function($templateCache) { - return { - restrict: 'E', - terminal: true, - compile: function(element, attr) { - if (attr.type == 'text/ng-template') { - var templateUrl = attr.id, - // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent - text = element[0].text; - - $templateCache.put(templateUrl, text); - } - } - }; -}]; - -/** - * @ngdoc directive - * @name ng.directive:select - * @restrict E - * - * @description - * HTML `SELECT` element with angular data-binding. - * - * # `ngOptions` - * - * Optionally `ngOptions` attribute can be used to dynamically generate a list of `