diff --git a/.asf.yaml b/.asf.yaml index a19e3a60a2ba..3b2f7691d136 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -50,10 +50,13 @@ github: rebase: false collaborators: - - gpordeus + - ingox + - gp-santos - erikbocks - Imvedansh - Damans227 + - jmsperu + - GaOrtiga protected_branches: ~ diff --git a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties b/.github/actions/install-nonoss/action.yml similarity index 66% rename from plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties rename to .github/actions/install-nonoss/action.yml index dcfe8d3537ff..39a03213c29d 100644 --- a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/module.properties +++ b/.github/actions/install-nonoss/action.yml @@ -14,5 +14,18 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -name=host-allocator-random -parent=allocator + +name: 'Install CloudStack Non-OSS' +description: 'Clones and installs the shapeblue/cloudstack-nonoss repository.' + +runs: + using: "composite" + steps: + - name: Install cloudstack-nonoss + shell: bash + run: | + git clone --depth 1 https://github.com/shapeblue/cloudstack-nonoss.git nonoss + cd nonoss + bash -x install-non-oss.sh + cd .. + rm -fr nonoss diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml new file mode 100644 index 000000000000..0f8425229242 --- /dev/null +++ b/.github/actions/setup-env/action.yml @@ -0,0 +1,58 @@ +# 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: 'Setup CloudStack Environment' +description: 'Sets up JDK (with Maven cache), optionally Python, and optionally APT build dependencies for CloudStack.' + +inputs: + java-version: + description: 'The JDK version to use' + required: false + default: '17' + install-python: + description: 'Whether to install Python 3.10' + required: false + default: 'false' + install-apt-deps: + description: 'Whether to install CloudStack APT build dependencies' + required: false + default: 'false' + +runs: + using: "composite" + steps: + - name: Set up JDK ${{ inputs.java-version }} + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 + with: + java-version: ${{ inputs.java-version }} + distribution: 'adopt' + architecture: x64 + cache: 'maven' + + - name: Set up Python + if: ${{ inputs.install-python == 'true' }} + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.10' + architecture: x64 + + - name: Install Build Dependencies + if: ${{ inputs.install-apt-deps == 'true' }} + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y git uuid-runtime genisoimage netcat-openbsd ipmitool build-essential libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 libpython3-dev libssl-dev libffi-dev python3-openssl python3-dev python3-setuptools diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c33a1313436..4eb508f5f6f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,40 +16,27 @@ # under the License. name: Build - -on: [push, pull_request] - +on: + - push + - pull_request concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: contents: read - jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 - - - name: Set up JDK 17 - uses: actions/setup-java@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - distribution: 'temurin' - java-version: '17' - cache: 'maven' + persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v6 + - name: Setup Environment + uses: ./.github/actions/setup-env with: - python-version: '3.10' - architecture: 'x64' - - - name: Install Build Dependencies - run: | - sudo apt-get update - sudo apt-get install -y git uuid-runtime genisoimage netcat ipmitool build-essential libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 ipmitool libpython3-dev libssl-dev libffi-dev python3-openssl python3-dev python3-setuptools - + install-python: 'true' + install-apt-deps: 'true' - name: Env details run: | uname -a @@ -60,9 +47,8 @@ jobs: free -m nproc git status - + - name: Install Non-OSS + uses: ./.github/actions/install-nonoss - name: Noredist Build run: | - git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss && cd nonoss && bash -x install-non-oss.sh && cd .. - rm -fr nonoss mvn -B -P developer,systemvm -Dsimulator -Dnoredist clean install -T$(nproc) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df60179ceb51..e9d03bf1209b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,21 +16,56 @@ # under the License. name: Simulator CI - -on: [push, pull_request] - +on: + - push + - pull_request concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: contents: read - jobs: build: if: github.repository == 'apache/cloudstack' runs-on: ubuntu-24.04 - + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + persist-credentials: false + - name: Setup Environment + uses: ./.github/actions/setup-env + with: + install-python: 'true' + install-apt-deps: 'true' + - name: Env details + run: | + uname -a + whoami + javac -version + mvn -v + python3 --version + free -m + nproc + git status + ipmitool -V + - name: Build with Maven + run: | + mvn -B -P developer,systemvm -Dsimulator clean install -DskipTests=true -T$(nproc) + - name: Archive artifacts + run: | + mkdir -p /tmp/artifacts + tar -czf /tmp/artifacts/targets.tar.gz $(find . -name "target" -type d) tools/marvin/dist engine/schema/dist utils/conf + tar -czf /tmp/artifacts/m2-cloudstack.tar.gz -C ~/.m2/repository org/apache/cloudstack + - name: Upload artifacts + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: build-artifacts + path: /tmp/artifacts/ + test: + needs: build + if: github.repository == 'apache/cloudstack' + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -215,30 +250,16 @@ jobs: smoke/test_list_service_offerings smoke/test_list_storage_pools smoke/test_list_volumes"] - steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - - name: Set up JDK 17 - uses: actions/setup-java@v5 + persist-credentials: false + - name: Setup Environment + uses: ./.github/actions/setup-env with: - distribution: 'temurin' - java-version: '17' - cache: 'maven' - - - name: Set up Python - uses: actions/setup-python@v6 - with: - python-version: '3.10' - architecture: 'x64' - - - name: Install Build Dependencies - run: | - sudo apt-get update - sudo apt-get install -y git uuid-runtime genisoimage netcat-openbsd ipmitool build-essential libgcrypt20 libgpg-error-dev libgpg-error0 libopenipmi0 ipmitool libpython3-dev libssl-dev libffi-dev python3-openssl python3-dev python3-setuptools - + install-python: 'true' + install-apt-deps: 'true' - name: Setup IPMI Tool for CloudStack run: | # Create cloudstack-common directory if it doesn't exist @@ -256,55 +277,43 @@ jobs: /usr/share/cloudstack-common/ipmitool -C3 $@ EOF sudo chmod 755 /usr/bin/ipmitool - - name: Install Python dependencies run: | python3 -m pip install --user --upgrade urllib3 lxml paramiko nose texttable ipmisim pyopenssl pycryptodome mock flask netaddr pylint pycodestyle six astroid pynose - - name: Install jacoco dependencies run: | wget https://github.com/jacoco/jacoco/releases/download/v0.8.10/jacoco-0.8.10.zip unzip jacoco-0.8.10.zip -d jacoco - - - name: Env details - run: | - uname -a - whoami - javac -version - mvn -v - python3 --version - free -m - nproc - git status - ipmitool -V - - name: Setup MySQL Server run: | # https://github.com/actions/runner-images/blob/main/images/linux/Ubuntu2004-Readme.md#mysql sudo apt-get install -y mysql-server sudo systemctl start mysql - sudo mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY ''; FLUSH PRIVILEGES;" + sudo mysql -uroot -proot -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY ''; FLUSH PRIVILEGES;" sudo systemctl restart mysql sudo mysql -uroot -e "SELECT VERSION();" - - - name: Build with Maven + - name: Download artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: build-artifacts + path: /tmp/artifacts/ + - name: Extract artifacts run: | - mvn -B -P developer,systemvm -Dsimulator clean install -DskipTests=true -T$(nproc) - + tar -xzf /tmp/artifacts/targets.tar.gz + mkdir -p ~/.m2/repository + tar -xzf /tmp/artifacts/m2-cloudstack.tar.gz -C ~/.m2/repository - name: Setup Simulator Prerequisites run: | sudo python3 -m pip install --upgrade netaddr mysql-connector-python python3 -m pip install --user --upgrade tools/marvin/dist/[mM]arvin-*.tar.gz mvn -q -Pdeveloper -pl developer -Ddeploydb mvn -q -Pdeveloper -pl developer -Ddeploydb-simulator - - name: Generate jacoco-coverage.sh run: | echo "java -jar jacoco/lib/jacococli.jar report jacoco-it.exec \\" > jacoco-report.sh find . | grep "target/classes" | sed 's/\/classes\//\/classes /g' | awk '{print "--classfiles", $1, "\\"}' | sort |uniq >> jacoco-report.sh find . | grep "src/main/java" | sed 's/\/java\//\/java /g' | awk '{print "--sourcefiles", $1, "\\"}' | sort | uniq >> jacoco-report.sh echo "--xml jacoco-coverage.xml" >> jacoco-report.sh - - name: Start CloudStack Management Server with Simulator run: | export MAVEN_OPTS="-Xmx4096m -XX:MaxMetaspaceSize=800m -Djava.security.egd=file:/dev/urandom -javaagent:jacoco/lib/jacocoagent.jar=address=*,port=36320,output=tcpserver --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-opens=java.base/jdk.internal.reflect=ALL-UNNAMED" @@ -315,7 +324,6 @@ jobs: set -e echo -e "\nStarting Advanced Zone DataCenter deployment" python3 tools/marvin/marvin/deployDataCenter.py -i setup/dev/advdualzone.cfg 2>&1 || true - - name: Run Integration Tests with Simulator run: | mkdir -p integration-test-results/smoke/misc @@ -335,13 +343,12 @@ jobs: bash jacoco-report.sh mvn -Dsimulator -pl client jetty:stop 2>&1 find /tmp//MarvinLogs -type f -exec echo -e "Printing marvin logs {} :\n" \; -exec cat {} \; - - name: Integration Tests Result run: | echo -e "Simulator CI Test Results: (only failures listed)\n" python3 ./tools/marvin/xunit-reader.py integration-test-results/ - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: files: jacoco-coverage.xml fail_ci_if_error: true diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml deleted file mode 100644 index 88b10ac9178f..000000000000 --- a/.github/workflows/codecov.yml +++ /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. - -name: Coverage Check - -on: [pull_request, push] - -permissions: - contents: read - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - build: - if: github.repository == 'apache/cloudstack' - name: codecov - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Set up JDK 17 - uses: actions/setup-java@v5 - with: - distribution: 'temurin' - java-version: '17' - cache: 'maven' - - - name: Build CloudStack with Quality Checks - run: | - git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss - cd nonoss && bash -x install-non-oss.sh && cd .. - mvn -P quality -Dsimulator -Dnoredist clean install -T$(nproc) - - - uses: codecov/codecov-action@v4 - with: - files: ./client/target/site/jacoco-aggregate/jacoco.xml - fail_ci_if_error: true - flags: unittests - verbose: true - name: codecov - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 31a8746b85af..a2d581dcac20 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -35,14 +35,16 @@ jobs: language: ["actions"] steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: category: "Security" diff --git a/.github/workflows/daily-repo-status.lock.yml b/.github/workflows/daily-repo-status.lock.yml index 1d7e7eecd14d..c9b85fafafdd 100644 --- a/.github/workflows/daily-repo-status.lock.yml +++ b/.github/workflows/daily-repo-status.lock.yml @@ -54,11 +54,11 @@ jobs: comment_repo: "" steps: - name: Setup - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45. + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.45. with: destination: /opt/gh-aw/ - name: Check workflow file - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_WORKFLOW_FILE: "daily-repo-status.lock.yml" with: @@ -96,13 +96,13 @@ jobs: secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: - name: Setup - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45. + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.45. with: destination: /opt/gh-aw/ - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0. with: - persist-credentials: + persist-credentials: false - name: Create gh-aw temp run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir. - name: Configure Git @@ -120,7 +120,7 @@ jobs: id: checkout- if: | github.event. - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: @@ -132,7 +132,7 @@ jobs: await main(); - name: Generate agentic run id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: script: | const fs = require('fs'); @@ -469,7 +469,7 @@ jobs: } - name: Generate workflow - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: script: | const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); @@ -559,7 +559,7 @@ jobs: {{#runtime-import .github/workflows/daily-repo-status.md}} - name: Substitute - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt. GH_AW_GITHUB_ACTOR: ${{ github.actor }} @@ -589,7 +589,7 @@ jobs: } }); - name: Interpolate variables and render - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt. with: @@ -667,7 +667,7 @@ jobs: bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" - name: Redact secrets in if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -682,7 +682,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Safe if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0. + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0. with: name: safe- path: ${{ env.GH_AW_SAFE_OUTPUTS }} @@ -690,7 +690,7 @@ jobs: - name: Ingest agent id: if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" @@ -704,13 +704,13 @@ jobs: await main(); - name: Upload sanitized agent if: always() && env. - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0. + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0. with: name: agent- path: ${{ env.GH_AW_AGENT_OUTPUT }} if-no-files-found: - name: Upload engine output - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0. + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0. with: name: path: | @@ -719,7 +719,7 @@ jobs: if-no-files-found: - name: Parse agent logs for step if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ with: @@ -730,7 +730,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -755,7 +755,7 @@ jobs: - name: Upload agent if: always() continue-on-error: - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0. + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0. with: name: agent- path: | @@ -784,12 +784,12 @@ jobs: total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45. + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.45. with: destination: /opt/gh-aw/ - name: Download agent output continue-on-error: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0. + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent- path: /tmp/gh-aw/safeoutputs/ @@ -800,7 +800,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - name: Process No-Op id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: @@ -816,7 +816,7 @@ jobs: await main(); - name: Record Missing id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Daily Repo Status" @@ -831,7 +831,7 @@ jobs: await main(); - name: Handle Agent id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Daily Repo Status" @@ -851,7 +851,7 @@ jobs: await main(); - name: Handle No-Op id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Daily Repo Status" @@ -881,18 +881,18 @@ jobs: success: ${{ steps.parse_results.outputs.success }} steps: - name: Setup - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45. + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.45. with: destination: /opt/gh-aw/ - name: Download agent continue-on-error: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0. + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent- path: /tmp/gh-aw/threat-detection/ - name: Download agent output continue-on-error: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0. + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent- path: /tmp/gh-aw/threat-detection/ @@ -902,7 +902,7 @@ jobs: run: | echo "Agent output-types: $AGENT_OUTPUT_TYPES" - name: Setup threat - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: WORKFLOW_NAME: "Daily Repo Status" WORKFLOW_DESCRIPTION: "This workflow creates daily repo status reports. It gathers recent repository\nactivity (issues, PRs, discussions, releases, code changes) and generates\nengaging GitHub issues with productivity insights, community highlights,\nand project recommendations." @@ -955,7 +955,7 @@ jobs: XDG_CONFIG_HOME: /home/ - name: Parse threat detection id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -964,7 +964,7 @@ jobs: await main(); - name: Upload threat detection if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0. + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v6.0. with: name: threat-detection. path: /tmp/gh-aw/threat-detection/detection. @@ -993,12 +993,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45. + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.45. with: destination: /opt/gh-aw/ - name: Download agent output continue-on-error: - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0. + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent- path: /tmp/gh-aw/safeoutputs/ @@ -1009,7 +1009,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - name: Process Safe id: - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"labels\":[\"report\",\"daily-status\"],\"max\":1,\"title_prefix\":\"[repo-status] \"},\"missing_data\":{},\"missing_tool\":{}}" diff --git a/.github/workflows/docker-cloudstack-simulator.yml b/.github/workflows/docker-cloudstack-simulator.yml index 8d23ac449dd5..dc00968563d0 100644 --- a/.github/workflows/docker-cloudstack-simulator.yml +++ b/.github/workflows/docker-cloudstack-simulator.yml @@ -35,10 +35,10 @@ concurrency: jobs: build: if: github.repository == 'apache/cloudstack' - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Login to Docker Registry - uses: docker/login-action@v2 + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ${{ secrets.DOCKER_REGISTRY }} username: ${{ secrets.DOCKERHUB_USER }} @@ -47,7 +47,9 @@ jobs: - name: Set Docker repository name run: echo "DOCKER_REPOSITORY=apache" >> $GITHUB_ENV - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set ACS version run: echo "ACS_VERSION=$(grep '' pom.xml | head -2 | tail -1 | cut -d'>' -f2 |cut -d'<' -f1)" >> $GITHUB_ENV diff --git a/.github/workflows/issue-triage-agent.lock.yml b/.github/workflows/issue-triage-agent.lock.yml index 2410f7b9e457..258032f02c4a 100644 --- a/.github/workflows/issue-triage-agent.lock.yml +++ b/.github/workflows/issue-triage-agent.lock.yml @@ -53,11 +53,11 @@ jobs: comment_repo: "" steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.74.8 with: destination: /opt/gh-aw/actions - name: Check workflow file timestamps - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_FILE: "issue-triage-agent.lock.yml" with: @@ -91,7 +91,7 @@ jobs: secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.74.8 with: destination: /opt/gh-aw/actions - name: Checkout repository @@ -113,7 +113,7 @@ jobs: echo "Git configured with standard GitHub Actions identity" - name: Generate agentic run info id: generate_aw_info - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const fs = require('fs'); @@ -167,7 +167,7 @@ jobs: run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.18.0 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} @@ -459,7 +459,7 @@ jobs: } GH_AW_MCP_CONFIG_EOF - name: Generate workflow overview - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); @@ -552,7 +552,7 @@ jobs: {{#runtime-import .github/workflows/issue-triage-agent.md}} GH_AW_PROMPT_EOF - name: Substitute placeholders - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_GITHUB_ACTOR: ${{ github.actor }} @@ -582,7 +582,7 @@ jobs: } }); - name: Interpolate variables and render templates - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} @@ -661,7 +661,7 @@ jobs: bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" - name: Redact secrets in logs if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -676,7 +676,7 @@ jobs: SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Upload Safe Outputs if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: safe-output path: ${{ env.GH_AW_SAFE_OUTPUTS }} @@ -684,7 +684,7 @@ jobs: - name: Ingest agent output id: collect_output if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" @@ -698,13 +698,13 @@ jobs: await main(); - name: Upload sanitized agent output if: always() && env.GH_AW_AGENT_OUTPUT - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: agent-output path: ${{ env.GH_AW_AGENT_OUTPUT }} if-no-files-found: warn - name: Upload engine output files - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: agent_outputs path: | @@ -713,7 +713,7 @@ jobs: if-no-files-found: ignore - name: Parse agent logs for step summary if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ with: @@ -724,7 +724,7 @@ jobs: await main(); - name: Parse MCP Gateway logs for step summary if: always() - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -749,7 +749,7 @@ jobs: - name: Upload agent artifacts if: always() continue-on-error: true - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: agent-artifacts path: | @@ -780,12 +780,12 @@ jobs: total_count: ${{ steps.missing_tool.outputs.total_count }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.74.8 with: destination: /opt/gh-aw/actions - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-output path: /tmp/gh-aw/safeoutputs/ @@ -796,7 +796,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - name: Process No-Op Messages id: noop - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: 1 @@ -812,7 +812,7 @@ jobs: await main(); - name: Record Missing Tool id: missing_tool - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Issue Triage Agent" @@ -827,7 +827,7 @@ jobs: await main(); - name: Handle Agent Failure id: handle_agent_failure - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Issue Triage Agent" @@ -846,7 +846,7 @@ jobs: await main(); - name: Handle No-Op Message id: handle_noop_message - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Issue Triage Agent" @@ -874,18 +874,18 @@ jobs: success: ${{ steps.parse_results.outputs.success }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.74.8 with: destination: /opt/gh-aw/actions - name: Download agent artifacts continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-artifacts path: /tmp/gh-aw/threat-detection/ - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-output path: /tmp/gh-aw/threat-detection/ @@ -895,7 +895,7 @@ jobs: run: | echo "Agent output-types: $AGENT_OUTPUT_TYPES" - name: Setup threat detection - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "Issue Triage Agent" WORKFLOW_DESCRIPTION: "No description provided" @@ -948,7 +948,7 @@ jobs: XDG_CONFIG_HOME: /home/runner - name: Parse threat detection results id: parse_results - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -957,7 +957,7 @@ jobs: await main(); - name: Upload threat detection log if: always() - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: threat-detection.log path: /tmp/gh-aw/threat-detection/detection.log @@ -987,12 +987,12 @@ jobs: process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: - name: Setup Scripts - uses: github/gh-aw/actions/setup@58d1d157fbac0f1204798500faefc4f7461ebe28 # v0.45.0 + uses: github/gh-aw/actions/setup@0feed75a980b06f247abbbf80127f8eb2c19e2c5 # v0.74.8 with: destination: /opt/gh-aw/actions - name: Download agent output artifact continue-on-error: true - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: agent-output path: /tmp/gh-aw/safeoutputs/ @@ -1003,7 +1003,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" - name: Process Safe Outputs id: process_safe_outputs - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"allowed\":[\"bug\",\"feature\",\"enhancement\",\"documentation\",\"question\",\"help-wanted\",\"good-first-issue\"]},\"missing_data\":{},\"missing_tool\":{}}" diff --git a/.github/workflows/main-sonar-check.yml b/.github/workflows/main-sonar-check.yml index 7ccd6600ab97..ad58d07a9894 100644 --- a/.github/workflows/main-sonar-check.yml +++ b/.github/workflows/main-sonar-check.yml @@ -15,54 +15,51 @@ # specific language governing permissions and limitations # under the License. -name: Main Branch Sonar Quality Check - +name: Sonar Quality Check (Main) +permissions: + contents: read on: push: branches: - main - -permissions: - contents: read # to fetch code (actions/checkout) - pull-requests: write # for sonar to comment on pull-request - +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: build: if: github.repository == 'apache/cloudstack' - name: Main Sonar JaCoCo Build - runs-on: ubuntu-22.04 + name: Sonar JaCoCo Coverage + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 - - - name: Set up JDK17 - uses: actions/setup-java@v5 + persist-credentials: false + - name: Setup Environment + uses: ./.github/actions/setup-env with: - distribution: 'temurin' - java-version: '17' - cache: 'maven' - + install-python: 'true' + install-apt-deps: 'true' - name: Cache SonarCloud packages - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - - name: Cache local Maven repository - uses: actions/cache@v5 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-m2-${{ hashFiles('pom.xml', '*/pom.xml', '*/*/pom.xml', '*/*/*/pom.xml') }} - restore-keys: | - ${{ runner.os }}-m2 - - - name: Run Tests with Coverage + - name: Install Non-OSS + uses: ./.github/actions/install-nonoss + - name: Run Build and Tests with Coverage + run: mvn -B -T$(nproc) -P developer,systemvm,quality -Dsimulator -Dnoredist clean install + - name: Upload to SonarQube env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: | - git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss - cd nonoss && bash -x install-non-oss.sh && cd .. - mvn -T$(nproc) -P quality -Dsimulator -Dnoredist clean install org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=apache_cloudstack + run: mvn -B -P quality org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=apache_cloudstack -Dsonar.branch.name=${{ github.ref_name }} + - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 + with: + files: ./client/target/site/jacoco-aggregate/jacoco.xml + fail_ci_if_error: true + flags: unittests + verbose: true + name: codecov + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/merge-conflict-checker.yml b/.github/workflows/merge-conflict-checker.yml index a997cb94ccc0..f23719f7183a 100644 --- a/.github/workflows/merge-conflict-checker.yml +++ b/.github/workflows/merge-conflict-checker.yml @@ -17,28 +17,26 @@ name: "PR Merge Conflict Check" on: - push: - pull_request: - types: [opened, synchronize, reopened] + schedule: + - cron: '*/10 * * * *' + workflow_dispatch: -permissions: # added using https://github.com/step-security/secure-workflows - contents: read +permissions: {} concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true + group: "gh-aw-${{ github.workflow }}" jobs: triage: permissions: - pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs - runs-on: ubuntu-22.04 + pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs + runs-on: ubuntu-24.04 steps: - - name: Conflict Check - uses: eps1lon/actions-label-merge-conflict@v2.0.0 - with: - repoToken: "${{ secrets.GITHUB_TOKEN }}" - dirtyLabel: "status:has-conflicts" - removeOnDirtyLabel: "status:ready-for-review" - continueOnMissingPermissions: true - commentOnDirty: "This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch." + - name: Conflict Check + uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 # v3.0.3 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + dirtyLabel: "status:has-conflicts" + removeOnDirtyLabel: "status:ready-for-review" + continueOnMissingPermissions: true + commentOnDirty: "This pull request has merge conflicts. Dear author, please fix the conflicts and sync your branch with the base branch." diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 895a597659de..1ea00be9112f 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -29,17 +29,23 @@ concurrency: jobs: pre-commit: name: Run pre-commit - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Check Out - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: '3.11' + cache: 'pip' - name: Install - run: | - python -m pip install --upgrade pip - pip install pre-commit + run: pip install pre-commit - name: Set PY run: echo "PY=$(python -VV | sha256sum | cut -d' ' -f1)" >> $GITHUB_ENV - - uses: actions/cache@v5 + - name: Cache pre-commit environments + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.cache/pre-commit key: pre-commit|${{ env.PY }}|${{ hashFiles('.pre-commit-config.yaml') }} diff --git a/.github/workflows/rat.yml b/.github/workflows/rat.yml index 21b8e197d825..a36a956856e7 100644 --- a/.github/workflows/rat.yml +++ b/.github/workflows/rat.yml @@ -16,32 +16,27 @@ # under the License. name: License Check - -on: [push, pull_request] - +on: + - push + - pull_request concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: contents: read - jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 - - name: Set up JDK 17 - uses: actions/setup-java@v5 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - java-version: '17' - distribution: 'adopt' - architecture: x64 - cache: maven + persist-credentials: false + - name: Setup Environment + uses: ./.github/actions/setup-env + - name: Install Non-OSS + uses: ./.github/actions/install-nonoss - name: RAT licence checks run: | - git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss && cd nonoss && bash -x install-non-oss.sh && cd .. - rm -fr nonoss mvn -P developer,systemvm -Dsimulator -Dnoredist -pl . org.apache.rat:apache-rat-plugin:0.12:check - name: Rat Report if: always() diff --git a/.github/workflows/sonar-check.yml b/.github/workflows/sonar-check.yml index 9f5c3a194bc7..49a49d009580 100644 --- a/.github/workflows/sonar-check.yml +++ b/.github/workflows/sonar-check.yml @@ -16,58 +16,52 @@ # under the License. name: Sonar Quality Check - -on: [pull_request] - permissions: - contents: read # to fetch code (actions/checkout) - pull-requests: write # for sonar to comment on pull-request - + contents: read + pull-requests: write +on: + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: build: - if: github.repository == 'apache/cloudstack' && github.event.pull_request.head.repo.full_name == github.repository name: Sonar JaCoCo Coverage - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - ref: "refs/pull/${{ github.event.number }}/merge" fetch-depth: 0 - - - name: Set up JDK17 - uses: actions/setup-java@v5 + persist-credentials: false + - name: Setup Environment + uses: ./.github/actions/setup-env with: - distribution: 'temurin' - java-version: '17' - cache: 'maven' - + install-python: 'true' + install-apt-deps: 'true' - name: Cache SonarCloud packages - uses: actions/cache@v5 + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - - name: Cache local Maven repository - uses: actions/cache@v5 - with: - path: ~/.m2/repository - key: ${{ runner.os }}-m2-${{ hashFiles('pom.xml', '*/pom.xml', '*/*/pom.xml', '*/*/*/pom.xml') }} - restore-keys: | - ${{ runner.os }}-m2 - + - name: Install Non-OSS + uses: ./.github/actions/install-nonoss - name: Run Build and Tests with Coverage - id: coverage + run: mvn -B -T$(nproc) -P developer,systemvm,quality -Dsimulator -Dnoredist clean install + - name: Upload to SonarQube + if: github.repository == 'apache/cloudstack' && github.event.pull_request.head.repo.full_name == github.repository env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} PR_ID: ${{ github.event.pull_request.number }} HEADREF: ${{ github.event.pull_request.head.ref }} run: | - git clone https://github.com/shapeblue/cloudstack-nonoss.git nonoss - cd nonoss && bash -x install-non-oss.sh && cd .. - mvn -T$(nproc) -P quality -Dsimulator -Dnoredist clean install org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=apache_cloudstack -Dsonar.pullrequest.key="$PR_ID" -Dsonar.pullrequest.branch="$HEADREF" -Dsonar.pullrequest.github.repository=apache/cloudstack -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.summary_comment=true + mvn -B -P quality org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=apache_cloudstack -Dsonar.pullrequest.key="$PR_ID" -Dsonar.pullrequest.branch="$HEADREF" -Dsonar.pullrequest.github.repository=apache/cloudstack -Dsonar.pullrequest.provider=GitHub -Dsonar.pullrequest.github.summary_comment=true + - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 + with: + files: ./client/target/site/jacoco-aggregate/jacoco.xml + fail_ci_if_error: true + flags: unittests + verbose: true + name: codecov + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 842e4497a4ad..c0da5f98cc8e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -28,7 +28,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v10 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: stale-issue-message: 'This issue is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.' stale-pr-message: 'This PR is stale because it has been open for 120 days with no activity. It may be removed by administrators of this project at any time. Remove the stale label or comment to request for removal of it to prevent this.' @@ -41,7 +41,7 @@ jobs: days-before-pr-close: 240 exempt-issue-labels: 'gsoc,good-first-issue,long-term-plan' exempt-pr-labels: 'status:ready-for-merge,status:needs-testing,status:on-hold' - - uses: actions/stale@v10 + - uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 with: stale-issue-label: 'archive' days-before-stale: 240 diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 4580b6bbd5da..ff27809db8f6 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -28,15 +28,19 @@ permissions: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Node - uses: actions/setup-node@v5 + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 16 + cache: 'npm' + cache-dependency-path: 'ui/package-lock.json' - name: Env details run: | @@ -55,7 +59,7 @@ jobs: npm run lint npm run test:unit - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 if: github.repository == 'apache/cloudstack' with: working-directory: ui diff --git a/README.md b/README.md index a5aacb49f6b5..852674ab7b1f 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ Unrestricted (TSU) exception (see the BIS Export Administration Regulations, Sec The following provides more details on the included cryptographic software: * CloudStack makes use of JaSypt cryptographic libraries. -* CloudStack has a system requirement of MySQL, and uses native database encryption functionality. +* CloudStack requires a MySQL-compatible database (MariaDB or MySQL), and uses native database encryption functionality. * CloudStack makes use of the Bouncy Castle general-purpose encryption library. * CloudStack can optionally interact with and control OpenSwan-based VPNs. * CloudStack has a dependency on and makes use of JSch - a java SSH2 implementation. diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java index e26cc1e9f029..9af6c731fd24 100644 --- a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineTO.java @@ -51,6 +51,7 @@ public class VirtualMachineTO { private long minRam; private long maxRam; + private long requestedRam; private String hostName; private String arch; private String os; @@ -207,15 +208,20 @@ public long getMinRam() { return minRam; } - public void setRam(long minRam, long maxRam) { + public void setRam(long minRam, long maxRam, long requestedRam) { this.minRam = minRam; this.maxRam = maxRam; + this.requestedRam = requestedRam; } public long getMaxRam() { return maxRam; } + public long getRequestedRam() { + return requestedRam; + } + public String getHostName() { return hostName; } diff --git a/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java b/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java index 604720aaa290..5d028d31d5b6 100644 --- a/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java +++ b/api/src/main/java/com/cloud/agent/manager/allocator/HostAllocator.java @@ -22,19 +22,11 @@ import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.host.Host; import com.cloud.host.Host.Type; -import com.cloud.offering.ServiceOffering; import com.cloud.utils.component.Adapter; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; public interface HostAllocator extends Adapter { - /** - * @param UserVm vm - * @param ServiceOffering offering - **/ - boolean isVirtualMachineUpgradable(final VirtualMachine vm, final ServiceOffering offering); - /** * Determines which physical hosts are suitable to * allocate the guest virtual machines on @@ -49,31 +41,6 @@ public interface HostAllocator extends Adapter { public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo); - /** - * Determines which physical hosts are suitable to allocate the guest - * virtual machines on - * - * Allocators must set any other hosts not considered for allocation in the - * ExcludeList avoid. Thus the avoid set and the list of hosts suitable, - * together must cover the entire host set in the cluster. - * - * @param VirtualMachineProfile - * vmProfile - * @param DeploymentPlan - * plan - * @param GuestType - * type - * @param ExcludeList - * avoid - * @param int returnUpTo (use -1 to return all possible hosts) - * @param boolean considerReservedCapacity (default should be true, set to - * false if host capacity calculation should not look at reserved - * capacity) - * @return List List of hosts that are suitable for VM allocation - **/ - - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity); - /** * Determines which physical hosts are suitable to allocate the guest * virtual machines on diff --git a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java index 8f7e773070f0..22d796d4a775 100644 --- a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java +++ b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java @@ -70,7 +70,7 @@ public interface DeploymentPlanner extends Adapter { boolean canHandle(VirtualMachineProfile vm, DeploymentPlan plan, ExcludeList avoid); public enum AllocationAlgorithm { - random, firstfit, userdispersing; + random, firstfit, userdispersing, firstfitleastconsumed; } public enum PlannerResourceUsage { diff --git a/api/src/main/java/com/cloud/ha/Investigator.java b/api/src/main/java/com/cloud/ha/Investigator.java index 88d802a1ce44..00371d395f5a 100644 --- a/api/src/main/java/com/cloud/ha/Investigator.java +++ b/api/src/main/java/com/cloud/ha/Investigator.java @@ -26,17 +26,19 @@ public interface Investigator extends Adapter { * Returns if the vm is still alive. * * @param vm to work on. + * @return true if vm is alive, otherwise false */ - public boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM; + boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM; - public Status isAgentAlive(Host agent); + /** + * Returns the agent status of the host. + * + * @param host + * @return status of the host agent + */ + Status getHostAgentStatus(Host host); class UnknownVM extends Exception { - - /** - * - */ private static final long serialVersionUID = 1L; - }; } diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index 0846306f70f9..e41eb880ffd5 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -510,4 +510,6 @@ public void setIp6Address(String ip6Address) { Integer getPrivateMtu(); Integer getNetworkCidrSize(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/NetworkProfile.java b/api/src/main/java/com/cloud/network/NetworkProfile.java index 2e8efb489308..d690344a0e38 100644 --- a/api/src/main/java/com/cloud/network/NetworkProfile.java +++ b/api/src/main/java/com/cloud/network/NetworkProfile.java @@ -385,6 +385,11 @@ public Integer getNetworkCidrSize() { return networkCidrSize; } + @Override + public boolean getKeepMacAddressOnPublicNic() { + return true; + } + @Override public String toString() { return String.format("NetworkProfile %s", diff --git a/api/src/main/java/com/cloud/network/vpc/Vpc.java b/api/src/main/java/com/cloud/network/vpc/Vpc.java index b94089d2d433..a0686e2bf7d0 100644 --- a/api/src/main/java/com/cloud/network/vpc/Vpc.java +++ b/api/src/main/java/com/cloud/network/vpc/Vpc.java @@ -107,4 +107,6 @@ public enum State { String getIp6Dns2(); boolean useRouterIpAsResolver(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/vpc/VpcService.java b/api/src/main/java/com/cloud/network/vpc/VpcService.java index c1546609d2b7..3d0ba43263f5 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -58,7 +58,7 @@ public interface VpcService { */ Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, String displayText, String cidr, String networkDomain, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Boolean displayVpc, Integer publicMtu, Integer cidrSize, - Long asNumber, List bgpPeerIds, Boolean useVrIpResolver) throws ResourceAllocationException; + Long asNumber, List bgpPeerIds, Boolean useVrIpResolver, boolean keepMacAddressOnPublicNic) throws ResourceAllocationException; /** * Persists VPC record in the database @@ -104,7 +104,7 @@ Vpc createVpc(long zoneId, long vpcOffId, long vpcOwnerId, String vpcName, Strin * @throws ResourceUnavailableException if during restart some resources may not be available * @throws InsufficientCapacityException if for instance no address space, compute or storage is sufficiently available */ - Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException; + Vpc updateVpc(long vpcId, String vpcName, String displayText, String customId, Boolean displayVpc, Integer mtu, String sourceNatIp, Boolean keepMacAddressOnPublicNic) throws ResourceUnavailableException, InsufficientCapacityException; /** * Lists VPC(s) based on the parameters passed to the API call 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 6fb7cb0612c0..694830ea2f36 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -69,6 +69,8 @@ public class ApiConstants { public static final String BACKUP_VM_OFFERING_REMOVED = "vmbackupofferingremoved"; public static final String IS_BACKUP_VM_EXPUNGED = "isbackupvmexpunged"; public static final String BACKUP_TOTAL = "backuptotal"; + public static final String BALANCE = "balance"; + public static final String BALANCES = "balances"; public static final String BASE64_IMAGE = "base64image"; public static final String BGP_PEERS = "bgppeers"; public static final String BGP_PEER_IDS = "bgppeerids"; @@ -171,6 +173,7 @@ public class ApiConstants { public static final String DATACENTER_NAME = "datacentername"; public static final String DATADISKS_DETAILS = "datadisksdetails"; public static final String DATADISK_OFFERING_LIST = "datadiskofferinglist"; + public static final String DATE = "date"; public static final String DEFAULT_VALUE = "defaultvalue"; public static final String DELETE_PROTECTION = "deleteprotection"; public static final String DESCRIPTION = "description"; @@ -1356,6 +1359,13 @@ public class ApiConstants { public static final String OBJECT_STORAGE_LIMIT = "objectstoragelimit"; public static final String OBJECT_STORAGE_TOTAL = "objectstoragetotal"; + public static final String KEEP_MAC_ADDRESS_ON_PUBLIC_NIC = "keepmacaddressonpublicnic"; + + public static final String PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC = + "Indicates whether to use the same MAC address for the public NIC of VRs on the same network. If \"true\", when creating redundant routers or recreating" + + " a VR, CloudStack will use the same MAC address for the public NIC of all VRs. Otherwise, if \"false\", new public NICs will always have " + + " a new MAC address."; + public static final String PARAMETER_DESCRIPTION_ACTIVATION_RULE = "Quota tariff's activation rule. It can receive a JS script that results in either " + "a boolean or a numeric value: if it results in a boolean value, the tariff value will be applied according to the result; if it results in a numeric value, the " + "numeric value will be applied; if the result is neither a boolean nor a numeric value, the tariff will not be applied. If the rule is not informed, the tariff " + 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..d333a74fdb3b 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 @@ -63,6 +63,12 @@ public class ProvisionCertificateCmd extends BaseAsyncCmd { description = "Name of the CA service provider, otherwise the default configured provider plugin will be used") private String provider; + @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, + description = "When true, uses SSH to re-provision the agent's certificate, bypassing the NIO agent connection. " + + "Use this when agents are disconnected due to a CA change. Supported for KVM hosts and SystemVMs. Default is false", + since = "4.23.0") + private Boolean forced; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -79,6 +85,10 @@ public String getProvider() { return provider; } + public boolean isForced() { + return forced != null && forced; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -90,7 +100,7 @@ public void execute() { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId()); } - boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider()); + boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider(), isForced()); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); setResponseObject(response); 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 6804e4355ca8..cb427e659495 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 @@ -87,6 +87,7 @@ private void setupResponse(final boolean result, final String resourceUuid) { final HostHAResponse response = new HostHAResponse(); response.setId(resourceUuid); response.setProvider(getHaProvider().toLowerCase()); + response.setStatus(result); response.setResponseName(getCommandName()); setResponseObject(response); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java index abca619f82a7..4d6ef7419616 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/FindHostsForMigrationCmd.java @@ -78,7 +78,7 @@ public void execute() { for (Host host : result.first()) { HostForMigrationResponse hostResponse = _responseGenerator.createHostForMigrationResponse(host); Boolean suitableForMigration = false; - if (hostsWithCapacity.contains(host)) { + if (hostsWithCapacity != null && hostsWithCapacity.contains(host)) { suitableForMigration = true; } hostResponse.setSuitableForMigration(suitableForMigration); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java index e202dfad77ba..8f5e6c784d6e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ListHostsCmd.java @@ -252,7 +252,7 @@ protected ListResponse getHostResponses() { for (Host host : result.first()) { HostResponse hostResponse = _responseGenerator.createHostResponse(host, getDetails()); Boolean suitableForMigration = false; - if (hostsWithCapacity.contains(host)) { + if (hostsWithCapacity != null && hostsWithCapacity.contains(host)) { suitableForMigration = true; } hostResponse.setSuitableForMigration(suitableForMigration); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserCmd.java index 3427cef33661..a3dcf08c3a31 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserCmd.java @@ -22,7 +22,7 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.UserResponse; - +import org.apache.cloudstack.api.ApiArgValidator; import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.UserAccount; @@ -35,7 +35,7 @@ public class GetUserCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, required = true, description = "API key of the user") + @Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, required = true, description = "API key of the user", validations = {ApiArgValidator.NotNullOrEmpty}) private String apiKey; ///////////////////////////////////////////////////// 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..29b1e740b67e 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 @@ -21,6 +21,7 @@ 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.BaseAsyncCmd; @@ -102,6 +103,16 @@ public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + @Override + public Long getApiResourceId() { + return vmId; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.VirtualMachine; + } + @Override public String getEventType() { return EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN; 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..8a4053aa15da 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 @@ -123,7 +123,12 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE @Override public ApiCommandResourceType getApiResourceType() { - return ApiCommandResourceType.Backup; + return ApiCommandResourceType.VirtualMachine; + } + + @Override + public Long getApiResourceId() { + return vmId; } @Override 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..f6e17a2b3908 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 @@ -21,6 +21,7 @@ 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.BaseCmd; @@ -81,7 +82,7 @@ public class CreateBackupScheduleCmd extends BaseCmd { @Parameter(name = ApiConstants.QUIESCE_VM, type = CommandType.BOOLEAN, required = false, - description = "Quiesce the instance before checkpointing the disks for backup. Applicable only to NAS backup provider. " + + description = "Quiesce the Instance before checkpointing the disks for backup. Applicable only to NAS backup provider. " + "The filesystem is frozen before the backup starts and thawed immediately after. " + "Requires the instance to have the QEMU Guest Agent installed and running.", since = "4.21.0") @@ -139,4 +140,14 @@ public void execute() throws ServerApiException { public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public Long getApiResourceId() { + return vmId; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.VirtualMachine; + } } 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..62c2f03d8807 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 @@ -21,6 +21,7 @@ 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.BaseAsyncCmd; @@ -99,6 +100,16 @@ public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + @Override + public Long getApiResourceId() { + return vmId; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.VirtualMachine; + } + @Override public String getEventType() { return EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE; 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..c15e6f8de684 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 @@ -22,6 +22,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ACL; 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.BaseAsyncCmd; @@ -127,4 +128,14 @@ public String getEventType() { public String getEventDescription() { return "Restoring volume "+ volumeUuid + " from backup " + getResourceUuid(ApiConstants.BACKUP_ID) + " and attaching it to Instance " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } + + @Override + public Long getApiResourceId() { + return vmId; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.VirtualMachine; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java index cbf6df081b3b..ee5b8568e835 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkCmd.java @@ -199,6 +199,11 @@ public class CreateNetworkCmd extends BaseCmd implements UserCmd { @Parameter(name=ApiConstants.AS_NUMBER, type=CommandType.LONG, since = "4.20.0", description="the AS Number of the network") private Long asNumber; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -286,6 +291,10 @@ public String getSourceNatIP() { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + @Override public boolean isDisplay() { if(displayNetwork == null) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java index 2e638f1e2f76..7b6841d60976 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmd.java @@ -105,6 +105,11 @@ public class UpdateNetworkCmd extends BaseAsyncCustomIdCmd implements UserCmd { @Parameter(name = ApiConstants.SOURCE_NAT_IP, type = CommandType.STRING, description = "IPV4 address to be assigned to the public interface of the network router. This address must already be acquired for this network", since = "4.19") private String sourceNatIP; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -186,6 +191,10 @@ public String getSourceNatIP() { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java index 2adbbd664085..86309d7f28a5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmd.java @@ -130,6 +130,11 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { description="(optional) for NSX based VPCs: when set to true, use the VR IP as nameserver, otherwise use DNS1 and DNS2") private Boolean useVrIpResolver; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private boolean keepMacAddressOnPublicNic = true; + // /////////////////////////////////////////////////// // ///////////////// Accessors /////////////////////// // /////////////////////////////////////////////////// @@ -214,6 +219,10 @@ public boolean getUseVrIpResolver() { return BooleanUtils.toBoolean(useVrIpResolver); } + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java index f2327c9073f3..6cfdb895977d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmd.java @@ -69,6 +69,11 @@ public class UpdateVPCCmd extends BaseAsyncCustomIdCmd implements UserCmd { since = "4.19") private String sourceNatIP; + @Parameter(name = ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, + type = CommandType.BOOLEAN, since = "4.23.0", authorized = {RoleType.Admin}) + private Boolean keepMacAddressOnPublicNic; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -97,6 +102,10 @@ public String getSourceNatIP() { return sourceNatIP; } + public Boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java index 7c4b733a80f5..3a3663af2551 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NetworkResponse.java @@ -331,6 +331,10 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "The BGP peers for the network", since = "4.20.0") private Set bgpPeers; + @SerializedName(ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0") + private Boolean keepMacAddressOnPublicNic; + public NetworkResponse() {} public Boolean getDisplayNetwork() { @@ -702,4 +706,8 @@ public void setIpv6Dns1(String ipv6Dns1) { public void setIpv6Dns2(String ipv6Dns2) { this.ipv6Dns2 = ipv6Dns2; } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java index acfabb113502..34d50d5b9f92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcResponse.java @@ -185,6 +185,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "The BGP peers for the VPC", since = "4.20.0") private Set bgpPeers; + @SerializedName(ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, since = "4.23.0") + private Boolean keepMacAddressOnPublicNic; + public void setId(final String id) { this.id = id; } @@ -366,4 +370,8 @@ public void addBgpPeer(BgpPeerResponse bgpPeer) { } this.bgpPeers.add(bgpPeer); } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java index b0fb1ac73c21..d2ebdc25f1bd 100644 --- a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java +++ b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Map; +import com.trilead.ssh2.Connection; + import org.apache.cloudstack.framework.ca.CAProvider; import org.apache.cloudstack.framework.ca.CAService; import org.apache.cloudstack.framework.ca.Certificate; @@ -39,7 +41,10 @@ public interface CAManager extends CAService, Configurable, PluggableService { ConfigKey CAProviderPlugin = new ConfigKey<>("Advanced", String.class, "ca.framework.provider.plugin", "root", - "The CA provider plugin that is used for secure CloudStack management server-agent communication for encryption and authentication. Restart management server(s) when changed.", true); + "The CA provider plugin used for CloudStack internal certificate management (MS-agent encryption and authentication). " + + "The default 'root' provider auto-generates a CA on first startup, but also supports user-provided custom CA material " + + "via the ca.plugin.root.private.key, ca.plugin.root.public.key, and ca.plugin.root.ca.certificate settings. " + + "Restart management server(s) when changed.", false); ConfigKey CertKeySize = new ConfigKey<>("Advanced", Integer.class, "ca.framework.cert.keysize", @@ -85,6 +90,12 @@ public interface CAManager extends CAService, Configurable, PluggableService { "The actual implementation will depend on the configured CA provider.", false); + ConfigKey CaInjectDefaultTruststore = new ConfigKey<>("Advanced", Boolean.class, + "ca.framework.inject.default.truststore", "true", + "When true, injects the CA provider's certificate into the JVM default truststore on management server startup. " + + "This allows outgoing HTTPS connections from the management server to trust servers with certificates signed by the configured CA. " + + "Restart management server(s) when changed.", false); + /** * Returns a list of available CA provider plugins * @return returns list of CAProvider @@ -130,12 +141,26 @@ public interface CAManager extends CAService, Configurable, PluggableService { boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String provider); /** - * Provisions certificate for given active and connected agent host + * Provisions certificate for given agent host. + * When forced=true, uses SSH to re-provision bypassing the NIO agent connection (for disconnected agents). * @param host + * @param reconnect * @param provider + * @param forced when true, provisions via SSH instead of NIO; supports KVM hosts and SystemVMs * @return returns success/failure as boolean */ - boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider); + boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider, final boolean forced); + + /** + * Provisions certificate for a KVM host using an existing SSH connection. + * Runs keystore-setup to generate a CSR, issues a certificate, then runs keystore-cert-import. + * Used during host discovery and for forced re-provisioning when the NIO agent is unreachable. + * @param sshConnection active SSH connection to the KVM host + * @param agentIp IP address of the KVM host agent + * @param agentHostname hostname of the KVM host agent + * @param caProvider optional CA provider plugin name (null uses default) + */ + void provisionCertificateViaSsh(Connection sshConnection, String agentIp, String agentHostname, String caProvider); /** * Setups up a new keystore and generates CSR for a host diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java index acb2dc685976..9d56cffd6718 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/UpdateVPCCmdTest.java @@ -93,7 +93,7 @@ public void testExecute() throws ResourceUnavailableException, InsufficientCapac responseGenerator = Mockito.mock(ResponseGenerator.class); cmd._responseGenerator = responseGenerator; Mockito.verify(_vpcService, Mockito.times(0)).updateVpc(Mockito.anyLong(), Mockito.anyString(), Mockito.anyString(), - Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString()); + Mockito.anyString(), Mockito.anyBoolean(), Mockito.anyInt(), Mockito.anyString(), Mockito.anyBoolean()); } } diff --git a/client/pom.xml b/client/pom.xml index 7118f455ab5f..ba16cc5d34a2 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -377,11 +377,6 @@ cloud-plugin-explicit-dedication ${project.version} - - org.apache.cloudstack - cloud-plugin-host-allocator-random - ${project.version} - org.apache.cloudstack cloud-plugin-outofbandmanagement-driver-ipmitool @@ -721,17 +716,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} @@ -911,13 +906,13 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on false ${project.build.directory}/lib org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on false ${project.build.directory}/lib @@ -941,7 +936,7 @@ org.bouncycastle - bctls-jdk15on + bctls-jdk18on false ${project.build.directory}/lib @@ -976,9 +971,9 @@ 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 diff --git a/core/src/main/java/com/cloud/agent/api/CheckOnHostAnswer.java b/core/src/main/java/com/cloud/agent/api/CheckOnHostAnswer.java index 5a26b22ec6a5..41e266784db8 100644 --- a/core/src/main/java/com/cloud/agent/api/CheckOnHostAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/CheckOnHostAnswer.java @@ -38,6 +38,8 @@ public CheckOnHostAnswer(CheckOnHostCommand cmd, Boolean alive, String details) public CheckOnHostAnswer(CheckOnHostCommand cmd, String details) { super(cmd, false, details); + determined = false; + alive = false; } public boolean isDetermined() { @@ -47,5 +49,4 @@ public boolean isDetermined() { public boolean isAlive() { return alive; } - } diff --git a/core/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java b/core/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java index 94239f2900ef..72b5217604dc 100644 --- a/core/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java +++ b/core/src/main/java/com/cloud/agent/api/CheckOnHostCommand.java @@ -24,7 +24,7 @@ public class CheckOnHostCommand extends Command { HostTO host; - boolean reportCheckFailureIfOneStorageIsDown; + boolean reportIfHeartBeatFailedForOneStoragePool; protected CheckOnHostCommand() { } @@ -34,17 +34,17 @@ public CheckOnHostCommand(Host host) { setWait(20); } - public CheckOnHostCommand(Host host, boolean reportCheckFailureIfOneStorageIsDown) { + public CheckOnHostCommand(Host host, boolean reportIfHeartBeatFailedForOneStoragePool) { this(host); - this.reportCheckFailureIfOneStorageIsDown = reportCheckFailureIfOneStorageIsDown; + this.reportIfHeartBeatFailedForOneStoragePool = reportIfHeartBeatFailedForOneStoragePool; } public HostTO getHost() { return host; } - public boolean isCheckFailedOnOneStorage() { - return reportCheckFailureIfOneStorageIsDown; + public boolean shouldReportIfHeartBeatFailedForOneStoragePool() { + return reportIfHeartBeatFailedForOneStoragePool; } @Override diff --git a/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java b/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java index dc63b1ee746d..e2953e9c7ad7 100644 --- a/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ScaleVmCommand.java @@ -30,6 +30,7 @@ public class ScaleVmCommand extends Command { Integer maxSpeed; long minRam; long maxRam; + private boolean limitCpuUseChange; public VirtualMachineTO getVm() { return vm; @@ -43,7 +44,7 @@ public int getCpus() { return cpus; } - public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpeed, long minRam, long maxRam, boolean limitCpuUse) { + public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpeed, long minRam, long maxRam, boolean limitCpuUse, Double cpuQuotaPercentage, boolean limitCpuUseChange) { super(); this.vmName = vmName; this.cpus = cpus; @@ -52,6 +53,8 @@ public ScaleVmCommand(String vmName, int cpus, Integer minSpeed, Integer maxSpee this.minRam = minRam; this.maxRam = maxRam; this.vm = new VirtualMachineTO(1L, vmName, null, cpus, minSpeed, maxSpeed, minRam, maxRam, null, null, false, limitCpuUse, null); + this.vm.setCpuQuotaPercentage(cpuQuotaPercentage); + this.limitCpuUseChange = limitCpuUseChange; } public void setCpus(int cpus) { @@ -102,6 +105,10 @@ public VirtualMachineTO getVirtualMachine() { return vm; } + public boolean getLimitCpuUseChange() { + return limitCpuUseChange; + } + @Override public boolean executeInSequence() { return true; diff --git a/debian/changelog b/debian/changelog index 02251137e9d0..41f97748a0b5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -2,13 +2,19 @@ cloudstack (4.23.0.0-SNAPSHOT) unstable; urgency=low * Update the version to 4.23.0.0-SNAPSHOT - -- the Apache CloudStack project Thu, 30 Oct 2025 19:23:55 +0530 + -- the Apache CloudStack project Fri, 22 May 2026 10:20:00 -0300 + +cloudstack (4.22.1.0) unstable; urgency=low + + * Update the version to 4.22.1.0 -cloudstack (4.23.0.0-SNAPSHOT-SNAPSHOT) unstable; urgency=low + -- the Apache CloudStack project Mon, 11 May 2026 20:26:07 +0530 - * Update the version to 4.23.0.0-SNAPSHOT-SNAPSHOT +cloudstack (4.22.0.0) unstable; urgency=low - -- the Apache CloudStack project Thu, Aug 28 11:58:36 2025 +0530 + * Update the version to 4.22.0.0 + + -- the Apache CloudStack project Thu, 30 Oct 2025 19:23:55 +0530 cloudstack (4.21.0.0) unstable; urgency=low diff --git a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java index 57dc1b7bf728..e871bd8672ff 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -181,15 +181,6 @@ void orchestrateStart(String vmUuid, Map pa void advanceReboot(String vmUuid, Map params) throws InsufficientCapacityException, ResourceUnavailableException, ConcurrentOperationException, OperationTimedoutException; - /** - * Check to see if a virtual machine can be upgraded to the given service offering - * - * @param vm - * @param offering - * @return true if the host can handle the upgrade, false otherwise - */ - boolean isVirtualMachineUpgradable(final VirtualMachine vm, final ServiceOffering offering); - VirtualMachine findById(long vmId); void storageMigration(String vmUuid, Map volumeToPool); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java index b7b548fb9407..030c1277efe2 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/NetworkOrchestrationService.java @@ -220,6 +220,11 @@ Network createGuestNetwork(long networkOfferingId, String name, String displayTe Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; + Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner, + Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String ip6Gateway, String ip6Cidr, + Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException; + UserDataServiceProvider getPasswordResetProvider(Network network); UserDataServiceProvider getSSHKeyResetProvider(Network network); diff --git a/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java b/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java index 4c81c7359f25..abaf6ea967d0 100644 --- a/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java +++ b/engine/components-api/src/main/java/com/cloud/capacity/CapacityManager.java @@ -133,6 +133,20 @@ public interface CapacityManager { "capacity.calculate.workers", "1", "Number of worker threads to be used for capacities calculation", true); + ConfigKey KvmMemoryDynamicScalingCapacity = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, + Integer.class, "kvm.memory.dynamic.scaling.capacity", "0", + "Defines the maximum memory capacity in MiB for which VMs can be dynamically scaled to with KVM. " + + "The 'kvm.memory.dynamic.scaling.capacity' setting's value will be used to define the value of the " + + "'' element of domain XMLs. If it is set to a value less than or equal to '0', then the host's memory capacity will be considered.", + true, ConfigKey.Scope.Cluster); + + ConfigKey KvmCpuDynamicScalingCapacity = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, + Integer.class, "kvm.cpu.dynamic.scaling.capacity", "0", + "Defines the maximum vCPU capacity for which VMs can be dynamically scaled to with KVM. " + + "The 'kvm.cpu.dynamic.scaling.capacity' setting's value will be used to define the value of the " + + "'' element of domain XMLs. If it is set to a value less than or equal to '0', then the host's CPU cores capacity will be considered.", + true, ConfigKey.Scope.Cluster); + public boolean releaseVmCapacity(VirtualMachine vm, boolean moveFromReserved, boolean moveToReservered, Long hostId); void allocateVmCapacity(VirtualMachine vm, boolean fromLastHost); diff --git a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java index 3ae94479cea5..53bfcce27038 100644 --- a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java +++ b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java @@ -75,10 +75,10 @@ public interface HighAvailabilityManager extends Manager { + " which are registered for the HA event that were successful and are now ready to be purged.", true, Cluster); - public static final ConfigKey KvmHAFenceHostIfHeartbeatFailsOnStorage = new ConfigKey<>("Advanced", Boolean.class, "kvm.ha.fence.on.storage.heartbeat.failure", "false", + ConfigKey KvmHAFenceHostIfHeartbeatFailsOnStorage = new ConfigKey<>("Advanced", Boolean.class, "kvm.ha.fence.on.storage.heartbeat.failure", "false", "Proceed fencing the host even the heartbeat failed for only one storage pool", false, ConfigKey.Scope.Zone); - public enum WorkType { + enum WorkType { Migration, // Migrating VMs off of a host. Stop, // Stops a VM for storage pool migration purposes. This should be obsolete now. CheckStop, // Checks if a VM has been stopped. 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 71ecc73f325f..ead990b42b86 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -50,6 +50,7 @@ import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; +import com.cloud.hypervisor.KVMGuru; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -4017,19 +4018,6 @@ protected void runInContext() { } } - @Override - public boolean isVirtualMachineUpgradable(final VirtualMachine vm, final ServiceOffering offering) { - boolean isMachineUpgradable = true; - for (final HostAllocator allocator : hostAllocators) { - isMachineUpgradable = allocator.isVirtualMachineUpgradable(vm, offering); - if (!isMachineUpgradable) { - break; - } - } - - return isMachineUpgradable; - } - @Override public void reboot(final String vmUuid, final Map params) throws InsufficientCapacityException, ResourceUnavailableException { try { @@ -4469,11 +4457,6 @@ public void checkIfCanUpgrade(final VirtualMachine vmInstance, final ServiceOffe throw new InvalidParameterValueException("isSystem property is different for current service offering and new service offering"); } - if (!isVirtualMachineUpgradable(vmInstance, newServiceOffering)) { - throw new InvalidParameterValueException("Unable to upgrade virtual machine, not enough resources available " + "for an offering of " + - newServiceOffering.getCpu() + " cpu(s) at " + newServiceOffering.getSpeed() + " Mhz, and " + newServiceOffering.getRamSize() + " MB of memory"); - } - final List currentTags = StringUtils.csvTagsToList(currentDiskOffering.getTags()); final List newTags = StringUtils.csvTagsToList(newDiskOffering.getTags()); if (VolumeApiServiceImpl.MatchStoragePoolTagsWithDiskOffering.valueIn(vmInstance.getDataCenterId())) { @@ -5180,7 +5163,7 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old try { result = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); } catch (Exception ex) { - throw new RuntimeException("Unhandled exception", ex); + throw new RuntimeException("Unable to reconfigure VM.", ex); } if (result != null) { @@ -5193,22 +5176,29 @@ public VMInstanceVO reConfigureVm(final String vmUuid, final ServiceOffering old private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering oldServiceOffering, ServiceOffering newServiceOffering, boolean reconfiguringOnExistingHost) throws ResourceUnavailableException, ConcurrentOperationException { - final VMInstanceVO vm = _vmDao.findByUuid(vmUuid); + 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); + Long clusterId = hostVo.getClusterId(); + Float memoryOvercommitRatio = CapacityManager.MemOverprovisioningFactor.valueIn(clusterId); + Float cpuOvercommitRatio = CapacityManager.CpuOverprovisioningFactor.valueIn(clusterId); + boolean divideMemoryByOverprovisioning = HypervisorGuruBase.VmMinMemoryEqualsMemoryDividedByMemOverprovisioningFactor.valueIn(clusterId); + boolean divideCpuByOverprovisioning = HypervisorGuruBase.VmMinCpuSpeedEqualsCpuSpeedDividedByCpuOverprovisioningFactor.valueIn(clusterId); 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()); + Double cpuQuotaPercentage = null; + if (newServiceOffering.getLimitCpuUse() && vm.getHypervisorType().equals(HypervisorType.KVM)) { + KVMGuru kvmGuru = (KVMGuru) _hvGuruMgr.getGuru(vm.getHypervisorType()); + cpuQuotaPercentage = kvmGuru.getCpuQuotaPercentage(minSpeed, hostVo.getSpeed()); + } + + boolean limitCpuUseChange = oldServiceOffering.getLimitCpuUse() != newServiceOffering.getLimitCpuUse(); + ScaleVmCommand scaleVmCommand = new ScaleVmCommand(vm.getInstanceName(), newServiceOffering.getCpu(), minSpeed, newServiceOffering.getSpeed(), + minMemory * 1024L * 1024L, newServiceOffering.getRamSize() * 1024L * 1024L, + newServiceOffering.getLimitCpuUse(), cpuQuotaPercentage, limitCpuUseChange); scaleVmCommand.getVirtualMachine().setId(vm.getId()); scaleVmCommand.getVirtualMachine().setUuid(vm.getUuid()); @@ -5237,16 +5227,20 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old throw new CloudRuntimeException("Unable to scale vm due to " + (reconfigureAnswer == null ? "" : reconfigureAnswer.getDetails())); } - upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + if (reconfiguringOnExistingHost) { + _capacityMgr.releaseVmCapacity(vm, false, false, vm.getHostId()); + } + + boolean vmUpgraded = upgradeVmDb(vm.getId(), newServiceOffering, oldServiceOffering); + if (vmUpgraded) { + vm = _vmDao.findById(vm.getId()); + } 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); } 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 7d455e7d6dc9..4262ee701aab 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 @@ -863,6 +863,7 @@ private static NetworkVO getNetworkVO(long id, final NetworkOffering offering, f vpcId, offering.isRedundantRouter(), predefined.getExternalId()); vo.setDisplayNetwork(isDisplayNetworkEnabled == null || isDisplayNetworkEnabled); vo.setStrechedL2Network(offering.isSupportingStrechedL2()); + vo.setKeepMacAddressOnPublicNic(predefined.getKeepMacAddressOnPublicNic()); return vo; } @@ -2724,7 +2725,7 @@ public Network createPrivateNetwork(final long networkOfferingId, final String n return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, null, owner, null, pNtwk, pNtwk.getDataCenterId(), ACLType.Account, null, vpcId, null, null, true, null, null, null, true, null, null, - null, null, null, null, null, null); + null, null, null, null, null, null, true); } @Override @@ -2735,10 +2736,25 @@ public Network createGuestNetwork(final long networkOfferingId, final String nam final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { + return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, + networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, + isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, true); + } + + @Override + @DB + public Network createGuestNetwork(final long networkOfferingId, final String name, final String displayText, final String gateway, final String cidr, String vlanId, + boolean bypassVlanOverlapCheck, String networkDomain, final Account owner, final Long domainId, final PhysicalNetwork pNtwk, + final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr, + final Boolean isDisplayNetworkEnabled, final String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, + String routerIp, String routerIpv6, String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, + Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { // create Isolated/Shared/L2 network return createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, domainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, - isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize); + isDisplayNetworkEnabled, isolatedPvlan, isolatedPvlanType, externalId, false, routerIp, routerIpv6, + ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); } @DB @@ -2747,7 +2763,8 @@ private Network createGuestNetwork(final long networkOfferingId, final String na final long zoneId, final ACLType aclType, Boolean subdomainAccess, final Long vpcId, final String ip6Gateway, final String ip6Cidr, final Boolean isDisplayNetworkEnabled, 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) throws ConcurrentOperationException, InsufficientCapacityException, ResourceAllocationException { + 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); @@ -3095,6 +3112,7 @@ public Network doInTransaction(final TransactionStatus status) { } } userNetwork.setNetworkCidrSize(networkCidrSize); + userNetwork.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, isDisplayNetworkEnabled); Network network; diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java index 090b019334f4..5f5b2affee08 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDao.java @@ -218,7 +218,7 @@ int countHostsByMsResourceStateTypeAndHypervisorType(long msId, List listOrderedHostsHypervisorVersionsInDatacenter(long datacenterId, HypervisorType hypervisorType); - List findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags); + List findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags); List findClustersThatMatchHostTagRule(String computeOfferingTags); 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 2d8fcca6cdb7..99c9a979c3bf 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 @@ -1520,7 +1520,7 @@ private List findHostIdsByHostTags(String hostTags){ } } - public List findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags) { + public List findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags) { List hostTagVOList = _hostTagsDao.findHostRuleTags(); List result = new ArrayList<>(); for (HostTagVO rule: hostTagVOList) { @@ -1534,7 +1534,7 @@ public List findHostsWithTagRuleThatMatchComputeOferringTags(String comp public List findClustersThatMatchHostTagRule(String computeOfferingTags) { Set result = new HashSet<>(); - List hosts = findHostsWithTagRuleThatMatchComputeOferringTags(computeOfferingTags); + List hosts = findHostsWithTagRuleThatMatchComputeOfferingTags(computeOfferingTags); for (HostVO host: hosts) { result.add(host.getClusterId()); } 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 02abaacd854e..f2572ba91c21 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 @@ -203,9 +203,13 @@ public class NetworkVO implements Network { @Column(name = "private_mtu") Integer privateMtu; + @Column(name = "keep_mac_address_on_public_nic") + private boolean keepMacAddressOnPublicNic = true; + @Transient Integer networkCidrSize; + public NetworkVO() { uuid = UUID.randomUUID().toString(); } @@ -773,4 +777,13 @@ public Integer getNetworkCidrSize() { public void setNetworkCidrSize(Integer networkCidrSize) { this.networkCidrSize = networkCidrSize; } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + + public void setKeepMacAddressOnPublicNic(boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java index ccba6bb18893..606bdaaaa7a7 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PublicIpQuarantineDao.java @@ -19,9 +19,21 @@ import com.cloud.network.vo.PublicIpQuarantineVO; import com.cloud.utils.db.GenericDao; +import java.util.Date; +import java.util.List; + public interface PublicIpQuarantineDao extends GenericDao { PublicIpQuarantineVO findByPublicIpAddressId(long publicIpAddressId); PublicIpQuarantineVO findByIpAddress(String publicIpAddress); + + /** + * Returns a list of public IP addresses that are actively quarantined at the specified date and the previous owner differs from the specified user. + * + * @param userId used to check against the IP's previous owner; + * @param date used to check if the quarantine is active; + * @return a list of PublicIpQuarantineVOs. + */ + List listQuarantinedIpAddressesToUser(Long userId, Date date); } 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..0c47a0d36e30 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 @@ -26,6 +26,8 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import java.util.Date; +import java.util.List; @Component public class PublicIpQuarantineDaoImpl extends GenericDaoBase implements PublicIpQuarantineDao { @@ -33,6 +35,8 @@ public class PublicIpQuarantineDaoImpl extends GenericDaoBase ipAddressSearchBuilder; + private SearchBuilder quarantinedIpAddressesSearch; + @Inject IPAddressDao ipAddressDao; @@ -47,8 +51,16 @@ public void init() { publicIpAddressByIdSearch.join("quarantineJoin", ipAddressSearchBuilder, ipAddressSearchBuilder.entity().getId(), publicIpAddressByIdSearch.entity().getPublicIpAddressId(), JoinBuilder.JoinType.INNER); + quarantinedIpAddressesSearch = createSearchBuilder(); + quarantinedIpAddressesSearch.and("previousOwnerId", quarantinedIpAddressesSearch.entity().getPreviousOwnerId(), SearchCriteria.Op.NEQ); + quarantinedIpAddressesSearch.and(); + quarantinedIpAddressesSearch.op("removedIsNull", quarantinedIpAddressesSearch.entity().getRemoved(), SearchCriteria.Op.NULL); + quarantinedIpAddressesSearch.and("endDate", quarantinedIpAddressesSearch.entity().getEndDate(), SearchCriteria.Op.GT); + quarantinedIpAddressesSearch.cp(); + ipAddressSearchBuilder.done(); publicIpAddressByIdSearch.done(); + quarantinedIpAddressesSearch.done(); } @Override @@ -68,4 +80,14 @@ public PublicIpQuarantineVO findByIpAddress(String publicIpAddress) { return findOneBy(sc, filter); } + + @Override + public List listQuarantinedIpAddressesToUser(Long userId, Date date) { + SearchCriteria sc = quarantinedIpAddressesSearch.create(); + + sc.setParameters("previousOwnerId", userId); + sc.setParameters("endDate", date); + + return searchIncludingRemoved(sc, null, false, false); + } } 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 e942eadb8ffb..742d3f2f82ee 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 @@ -108,6 +108,9 @@ public class VpcVO implements Vpc { @Column(name = "use_router_ip_resolver") boolean useRouterIpResolver = false; + @Column(name = "keep_mac_address_on_public_nic") + private boolean keepMacAddressOnPublicNic = true; + @Transient boolean rollingRestart = false; @@ -321,4 +324,13 @@ public boolean useRouterIpAsResolver() { public void setUseRouterIpResolver(boolean useRouterIpResolver) { this.useRouterIpResolver = useRouterIpResolver; } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + + public void setKeepMacAddressOnPublicNic(boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } } 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 292bafefbb65..2acb4138d234 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java @@ -1073,7 +1073,7 @@ protected void updateRegisteredTemplateDetails(Long templateId, MetadataTemplate } Hypervisor.HypervisorType hypervisorType = templateDetails.getHypervisorType(); updateSystemVMEntries(templateId, hypervisorType); - updateConfigurationParams(hypervisorType, templateDetails.getName(), zoneId); + updateConfigurationParams(hypervisorType, templateVO.getName(), zoneId); } protected void updateTemplateUrlChecksumAndGuestOsId(VMTemplateVO templateVO, 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 6ffa7cd59626..019d152ce5c3 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 @@ -358,7 +358,8 @@ protected void init() { IdsPowerStateSelectSearch.entity().getPowerHostId(), IdsPowerStateSelectSearch.entity().getPowerState(), IdsPowerStateSelectSearch.entity().getPowerStateUpdateCount(), - IdsPowerStateSelectSearch.entity().getPowerStateUpdateTime()); + IdsPowerStateSelectSearch.entity().getPowerStateUpdateTime(), + IdsPowerStateSelectSearch.entity().getState()); IdsPowerStateSelectSearch.done(); CountByOfferingId = createSearchBuilder(Integer.class); @@ -1105,10 +1106,14 @@ public Map updatePowerState( private boolean isPowerStateInSyncWithInstanceState(final VirtualMachine.PowerState powerState, final long powerHostId, final VMInstanceVO instance) { State instanceState = instance.getState(); + if (instanceState == null) { + logger.warn("VM {} has null instance state during power state sync check, treating as out of sync", instance); + return false; + } if ((powerState == VirtualMachine.PowerState.PowerOff && instanceState == State.Running) || (powerState == VirtualMachine.PowerState.PowerOn && instanceState == State.Stopped)) { HostVO instanceHost = hostDao.findById(instance.getHostId()); - HostVO powerHost = powerHostId == instance.getHostId() ? instanceHost : hostDao.findById(powerHostId); + HostVO powerHost = instance.getHostId() != null && powerHostId == instance.getHostId() ? instanceHost : hostDao.findById(powerHostId); logger.debug("VM: {} on host: {} and power host : {} is in {} state, but power state is {}", instance, instanceHost, powerHost, instanceState, powerState); return false; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql index 54baf226ac43..2f104568c140 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql @@ -18,3 +18,9 @@ --; -- Schema upgrade cleanup from 4.22.0.0 to 4.22.1.0 --; + +-- Entries remaining on `cloud`.`resource_reservation` during the upgrade process are stale, so delete them. +-- This script was added to normalize volume/primary storage reservations that got stuck due to a bug on VM deployment, +-- but it is more interesting to introduce a smarter logic to clean these stale reservations in the future without the need +-- for upgrades (for instance, by having a heartbeat_time column for the reservations and automatically cleaning old entries). +DELETE FROM `cloud`.`resource_reservation`; 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 c0feb06e76ac..dfa5fa8f3a14 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 @@ -127,3 +127,25 @@ CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` ( PRIMARY KEY (`id`), CONSTRAINT `fk_quota_tariff_usage__tariff_id` FOREIGN KEY (`tariff_id`) REFERENCES `cloud_usage`.`quota_tariff` (`id`), CONSTRAINT `fk_quota_tariff_usage__quota_usage_id` FOREIGN KEY (`quota_usage_id`) REFERENCES `cloud_usage`.`quota_usage` (`id`)); + +-- 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'); + +-- Creates the 'kvm.memory.dynamic.scaling.capacity' and, for already active ACS environments, +-- initializes it with the value of the setting 'vm.serviceoffering.ram.size.max' +INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `default_value`, `updated`, `scope`, `is_dynamic`, `group_id`, `subgroup_id`, `display_text`, `description`) +SELECT 'Advanced', 'DEFAULT', 'CapacityManager', 'kvm.memory.dynamic.scaling.capacity', `cfg`.`value`, 0, NULL, 4, 1, 6, 27, + 'KVM memory dynamic scaling capacity', 'Defines the maximum memory capacity in MiB for which VMs can be dynamically scaled to with KVM. The ''kvm.memory.dynamic.scaling.capacity'' setting''s value will be used to define the value of the '''' element of domain XMLs. If it is set to a value less than or equal to ''0'', then the host''s memory capacity will be considered.' +FROM `cloud`.`configuration` `cfg` +WHERE NOT EXISTS (SELECT 1 FROM `cloud`.`configuration` WHERE `name` = 'kvm.memory.dynamic.scaling.capacity') + AND `cfg`.`name` = 'vm.serviceoffering.ram.size.max'; + +-- Creates the 'kvm.cpu.dynamic.scaling.capacity' and, for already active ACS environments, +-- initializes it with the value of the setting 'vm.serviceoffering.cpu.cores.max' +INSERT INTO `cloud`.`configuration` (`category`, `instance`, `component`, `name`, `value`, `default_value`, `updated`, `scope`, `is_dynamic`, `group_id`, `subgroup_id`, `display_text`, `description`) +SELECT 'Advanced', 'DEFAULT', 'CapacityManager', 'kvm.cpu.dynamic.scaling.capacity', `cfg`.`value`, 0, NULL, 4, 1, 6, 27, + 'KVM CPU dynamic scaling capacity', 'Defines the maximum vCPU capacity for which VMs can be dynamically scaled to with KVM. The ''kvm.cpu.dynamic.scaling.capacity'' setting''s value will be used to define the value of the '''' element of domain XMLs. If it is set to a value less than or equal to ''0'', then the host''s CPU cores capacity will be considered.' +FROM `cloud`.`configuration` `cfg` +WHERE NOT EXISTS (SELECT 1 FROM `cloud`.`configuration` WHERE `name` = 'kvm.cpu.dynamic.scaling.capacity') + AND `cfg`.`name` = 'vm.serviceoffering.cpu.cores.max'; 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 8028e78c9073..51db952eb613 100644 --- a/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java +++ b/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java @@ -570,18 +570,19 @@ public void updateRegisteredTemplateDetails_UpdatesTemplateSuccessfully() { SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class); + String templateName = "templateName"; + when(templateVO.getName()).thenReturn(templateName); GuestOSVO guestOS = Mockito.mock(GuestOSVO.class); when(templateDetails.getGuestOs()).thenReturn("Debian"); when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); - when(templateDetails.getName()).thenReturn("templateName"); when(vmTemplateDao.findById(templateId)).thenReturn(templateVO); when(guestOSDao.findOneByDisplayName("Debian")).thenReturn(guestOS); when(guestOS.getId()).thenReturn(10L); when(vmTemplateDao.update(templateVO.getId(), templateVO)).thenReturn(true); doNothing().when(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM); doNothing().when(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM, - "templateName", zoneId); + templateName, zoneId); systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId); @@ -590,7 +591,7 @@ public void updateRegisteredTemplateDetails_UpdatesTemplateSuccessfully() { verify(vmTemplateDao).update(templateVO.getId(), templateVO); verify(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM); verify(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM, - "templateName", zoneId); + templateName, zoneId); } @Test @@ -620,16 +621,17 @@ public void updateRegisteredTemplateDetails_SkipsGuestOSUpdateWhenNotFound() { SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class); + String templateName = "templateName"; + when(templateVO.getName()).thenReturn(templateName); when(templateDetails.getGuestOs()).thenReturn("NonExistentOS"); when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); - when(templateDetails.getName()).thenReturn("templateName"); when(vmTemplateDao.findById(templateId)).thenReturn(templateVO); when(guestOSDao.findOneByDisplayName("NonExistentOS")).thenReturn(null); when(vmTemplateDao.update(templateVO.getId(), templateVO)).thenReturn(true); doNothing().when(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM); doNothing().when(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM, - "templateName", zoneId); + templateName, zoneId); systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId); @@ -637,7 +639,7 @@ public void updateRegisteredTemplateDetails_SkipsGuestOSUpdateWhenNotFound() { verify(vmTemplateDao).update(templateVO.getId(), templateVO); verify(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM); verify(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM, - "templateName", zoneId); + templateName, zoneId); } @Test 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 436f991afbd1..8731e8791ddd 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 @@ -395,9 +395,9 @@ public AsyncCallFuture expungeVolumeAsync(VolumeInfo volume) { } // Find out if the volume is at state of download_in_progress on secondary storage - VolumeDataStoreVO volumeStore = _volumeStoreDao.findByVolume(volume.getId()); - if (volumeStore != null) { - if (volumeStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) { + VolumeDataStoreVO volumeOnImageStore = _volumeStoreDao.findByVolume(volume.getId()); + if (volumeOnImageStore != null) { + if (volumeOnImageStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_IN_PROGRESS) { String msg = String.format("Volume: %s is currently being uploaded; can't delete it.", volume); logger.debug(msg); result.setSuccess(false); @@ -416,10 +416,10 @@ public AsyncCallFuture expungeVolumeAsync(VolumeInfo volume) { if (!volumeExistsOnPrimary(vol)) { // not created on primary store - if (volumeStore == null) { + if (volumeOnImageStore == null) { // also not created on secondary store if (logger.isDebugEnabled()) { - logger.debug("Marking volume that was never created as destroyed: " + vol); + logger.debug("Marking volume that was never created as destroyed: {}", vol); } VMTemplateVO template = templateDao.findById(vol.getTemplateId()); if (template != null && !template.isDeployAsIs()) { @@ -435,11 +435,21 @@ public AsyncCallFuture expungeVolumeAsync(VolumeInfo volume) { if (volume.getDataStore().getRole() == DataStoreRole.Image) { // no need to change state in volumes table volume.processEventOnly(Event.DestroyRequested); + if (volumeOnImageStore == null) { + logger.debug("Volume {} doesn't exist on image store, no need to delete", vol); + future.complete(result); + return future; + } } else if (volume.getDataStore().getRole() == DataStoreRole.Primary) { if (vol.getState() == Volume.State.Expunging) { logger.info("Volume {} is already in Expunging, retrying", volume); } volume.processEvent(Event.ExpungeRequested); + if (!volumeExistsOnPrimary(vol)) { + logger.debug("Volume {} doesn't exist on primary storage, no need to delete", vol); + future.complete(result); + return future; + } } DeleteVolumeContext context = new DeleteVolumeContext<>(null, vo, future); @@ -460,13 +470,11 @@ public void ensureVolumeIsExpungeReady(long volumeId) { private boolean volumeExistsOnPrimary(VolumeVO vol) { Long poolId = vol.getPoolId(); - if (poolId == null) { return false; } PrimaryDataStore primaryStore = dataStoreMgr.getPrimaryDataStore(poolId); - if (primaryStore == null) { return false; } @@ -476,8 +484,7 @@ private boolean volumeExistsOnPrimary(VolumeVO vol) { } String volumePath = vol.getPath(); - - if (volumePath == null || volumePath.trim().isEmpty()) { + if (StringUtils.isBlank(volumePath)) { return false; } 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 1422338ddc99..f6fd08b6da2c 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 @@ -1663,14 +1663,14 @@ public Extension getExtensionForCluster(long clusterId) { public List getExtensionReservedResourceDetails(long extensionId) { ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(extensionId, ApiConstants.RESERVED_RESOURCE_DETAILS); - if (detailsVO == null || !StringUtils.isNotBlank(detailsVO.getValue())) { - return Collections.emptyList(); - } List reservedDetails = new ArrayList<>(); - String[] parts = detailsVO.getValue().split(","); - for (String part : parts) { - if (StringUtils.isNotBlank(part)) { - reservedDetails.add(part.trim()); + if (detailsVO != null && StringUtils.isNotBlank(detailsVO.getValue())) { + String[] parts = detailsVO.getValue().split(","); + for (String part : parts) { + String trimmedPart = part.trim(); + if (StringUtils.isNotBlank(trimmedPart)) { + reservedDetails.add(trimmedPart); + } } } addInbuiltExtensionReservedResourceDetails(extensionId, reservedDetails); 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..58f51eae9fcd 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 @@ -150,8 +150,9 @@ protected void processQuotaBalanceForAccount(AccountVO accountVo, List> periods = accountQuotaUsages.stream() @@ -215,7 +216,7 @@ protected BigDecimal retrieveBalanceForUsageCalculation(long accountId, long dom logger.debug(String.format("Persisting the first quota balance [%s] for account [%s].", firstBalance, accountToString)); _quotaBalanceDao.saveQuotaBalance(firstBalance); } else { - QuotaBalanceVO lastRealBalance = _quotaBalanceDao.findLastBalanceEntry(accountId, domainId, startDate); + QuotaBalanceVO lastRealBalance = _quotaBalanceDao.getLastQuotaBalanceEntry(accountId, domainId, startDate); if (lastRealBalance == null) { logger.warn("Account [{}] has quota usage entries, however it does not have a quota balance.", accountToString); @@ -244,7 +245,7 @@ protected void saveQuotaAccount(long accountId, BigDecimal aggregatedUsage, Date } protected BigDecimal aggregateCreditBetweenDates(Long accountId, Long domainId, Date startDate, Date endDate, String accountToString) { - List creditsReceived = _quotaBalanceDao.findCreditBalance(accountId, domainId, startDate, endDate); + List creditsReceived = _quotaBalanceDao.findCreditBalances(accountId, domainId, startDate, endDate); logger.debug("Account [{}] has [{}] credit entries before [{}].", accountToString, creditsReceived.size(), DateUtil.displayDateInTimezone(usageAggregationTimeZone, endDate)); 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 cd87052e878c..23020292027c 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 @@ -35,6 +35,7 @@ import javax.inject.Inject; import com.cloud.storage.StoragePoolTagVO; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleVO; import org.apache.cloudstack.acl.dao.RoleDao; import org.apache.cloudstack.backup.BackupOfferingVO; @@ -783,10 +784,21 @@ protected void loadPresetVariableValueForNetwork(UsageVO usageRecord, Value valu value.setId(network.getUuid()); value.setName(network.getName()); value.setState(usageRecord.getState()); - + value.setResourceCounting(getPresetVariableValueNetworkResourceCounting(networkId)); value.setNetworkOffering(getPresetVariableValueNetworkOffering(network.getNetworkOfferingId())); } + protected ResourceCounting getPresetVariableValueNetworkResourceCounting(Long networkId) { + ResourceCounting resourceCounting = new ResourceCounting(); + List vmInstancesVO = vmInstanceDao.listNonRemovedVmsByTypeAndNetwork(networkId, VirtualMachine.Type.User); + int runningVms = (int) vmInstancesVO.stream().filter(vm -> vm.getState().equals(VirtualMachine.State.Running)).count(); + int stoppedVms = (int) vmInstancesVO.stream().filter(vm -> vm.getState().equals(VirtualMachine.State.Stopped)).count(); + + resourceCounting.setRunningVms(runningVms); + resourceCounting.setStoppedVms(stoppedVms); + return resourceCounting; + } + protected GenericPresetVariable getPresetVariableValueNetworkOffering(Long networkOfferingId) { NetworkOfferingVO networkOfferingVo = networkOfferingDao.findByIdIncludingRemoved(networkOfferingId); validateIfObjectIsNull(networkOfferingVo, networkOfferingId, "network offering"); diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ResourceCounting.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ResourceCounting.java new file mode 100644 index 000000000000..75049c3486a3 --- /dev/null +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/ResourceCounting.java @@ -0,0 +1,52 @@ +// 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.quota.activationrule.presetvariables; + + +import org.apache.cloudstack.quota.constant.QuotaTypes; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class ResourceCounting { + + @PresetVariableDefinition(description = "The number of running user instances.", supportedTypes = {QuotaTypes.NETWORK}) + private int runningVms; + @PresetVariableDefinition(description = "The number of stopped user instances.", supportedTypes = {QuotaTypes.NETWORK}) + private int stoppedVms; + + public int getRunningVms() { + return runningVms; + } + + public void setRunningVms(int runningVms) { + this.runningVms = runningVms; + } + + public int getStoppedVms() { + return stoppedVms; + } + + public void setStoppedVms(int stoppedVms) { + this.stoppedVms = stoppedVms; + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java index aff2c040e742..286fe5c60fd5 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/activationrule/presetvariables/Value.java @@ -84,6 +84,9 @@ public class Value extends GenericPresetVariable { @PresetVariableDefinition(description = "Backup offering of the backup.", supportedTypes = {QuotaTypes.BACKUP}) private BackupOffering backupOffering; + @PresetVariableDefinition(description = "The amount of resources of the usage type.") + private ResourceCounting resourceCounting; + @PresetVariableDefinition(description = "The hypervisor where the resource was deployed. Values can be: XenServer, KVM, VMware, Hyperv, BareMetal, Ovm, Ovm3 and LXC.", supportedTypes = {QuotaTypes.RUNNING_VM, QuotaTypes.ALLOCATED_VM, QuotaTypes.VM_SNAPSHOT, QuotaTypes.SNAPSHOT}) private String hypervisorType; @@ -262,6 +265,14 @@ public void setState(String state) { this.state = state; } + public ResourceCounting getResourceCounting() { + return resourceCounting; + } + + public void setResourceCounting(ResourceCounting resourceCounting) { + this.resourceCounting = resourceCounting; + } + public GenericPresetVariable getNetworkOffering() { return networkOffering; } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java index c694eaeefbe8..2964746bdf06 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDao.java @@ -28,16 +28,14 @@ public interface QuotaBalanceDao extends GenericDao { QuotaBalanceVO saveQuotaBalance(QuotaBalanceVO qb); - List findCreditBalance(Long accountId, Long domainId, Date startDate, Date endDate); + List findCreditBalances(Long accountId, Long domainId, Date startDate, Date endDate); - QuotaBalanceVO findLastBalanceEntry(Long accountId, Long domainId, Date beforeThis); + QuotaBalanceVO getLastQuotaBalanceEntry(Long accountId, Long domainId, Date beforeThis); QuotaBalanceVO findLaterBalanceEntry(Long accountId, Long domainId, Date afterThis); - List findQuotaBalance(Long accountId, Long domainId, Date startDate, Date endDate); + List listQuotaBalances(Long accountId, Long domainId, Date startDate, Date endDate); - List lastQuotaBalanceVO(Long accountId, Long domainId, Date startDate); - - BigDecimal lastQuotaBalance(Long accountId, Long domainId, Date startDate); + BigDecimal getLastQuotaBalance(Long accountId, Long domainId); } diff --git a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java index 01272d1a6184..21553ebd27b0 100644 --- a/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java +++ b/framework/quota/src/main/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImpl.java @@ -18,11 +18,14 @@ import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Collections; import java.util.Date; import java.util.List; import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.time.DateUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.springframework.stereotype.Component; import com.cloud.utils.db.Filter; @@ -32,160 +35,104 @@ import com.cloud.utils.db.Transaction; import com.cloud.utils.db.TransactionCallback; import com.cloud.utils.db.TransactionLegacy; -import com.cloud.utils.db.TransactionStatus; @Component public class QuotaBalanceDaoImpl extends GenericDaoBase implements QuotaBalanceDao { + private static final Logger logger = LogManager.getLogger(QuotaBalanceDaoImpl.class); @Override - public QuotaBalanceVO findLastBalanceEntry(final Long accountId, final Long domainId, final Date beforeThis) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaBalanceVO doInTransaction(final TransactionStatus status) { - List quotaBalanceEntries = new ArrayList<>(); - Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 1L); - QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); - qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); - qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); - qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); + public QuotaBalanceVO getLastQuotaBalanceEntry(final Long accountId, final Long domainId, final Date beforeThis) { + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> { + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 1L); + QueryBuilder qb = getQuotaBalanceQueryBuilder(accountId, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); + + if (beforeThis != null) { qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LT, beforeThis); - quotaBalanceEntries = search(qb.create(), filter); - return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null; } + + List quotaBalanceEntries = search(qb.create(), filter); + return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null; }); } @Override public QuotaBalanceVO findLaterBalanceEntry(final Long accountId, final Long domainId, final Date afterThis) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaBalanceVO doInTransaction(final TransactionStatus status) { - List quotaBalanceEntries = new ArrayList<>(); - Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, 1L); - QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); - qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); - qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); - qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); - qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT, afterThis); - quotaBalanceEntries = search(qb.create(), filter); - return quotaBalanceEntries.size() > 0 ? quotaBalanceEntries.get(0) : null; - } + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> { + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, 1L); + QueryBuilder qb = getQuotaBalanceQueryBuilder(accountId, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GT, afterThis); + + List quotaBalanceEntries = search(qb.create(), filter); + return !quotaBalanceEntries.isEmpty() ? quotaBalanceEntries.get(0) : null; }); } @Override public QuotaBalanceVO saveQuotaBalance(final QuotaBalanceVO qb) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback() { - @Override - public QuotaBalanceVO doInTransaction(final TransactionStatus status) { - return persist(qb); - } - }); + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback) status -> persist(qb)); } @Override - public List findCreditBalance(final Long accountId, final Long domainId, final Date lastbalancedate, final Date beforeThis) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { - @Override - public List doInTransaction(final TransactionStatus status) { - if ((lastbalancedate != null) && (beforeThis != null) && lastbalancedate.before(beforeThis)) { - Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, Long.MAX_VALUE); - QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); - qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); - qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); - qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT, 0); - qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, lastbalancedate, beforeThis); - return search(qb.create(), filter); - } else { - return new ArrayList(); - } + public List findCreditBalances(final Long accountId, final Long domainId, final Date startDate, final Date endDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback>) status -> { + if (ObjectUtils.anyNull(startDate, endDate) || startDate.after(endDate)) { + return new ArrayList<>(); } - }); - } - @Override - public List findQuotaBalance(final Long accountId, final Long domainId, final Date startDate, final Date endDate) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { - @Override - public List doInTransaction(final TransactionStatus status) { - List quotaUsageRecords = null; - QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); - if (accountId != null) { - qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); - } - if (domainId != null) { - qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); - } - if ((startDate != null) && (endDate != null) && startDate.before(endDate)) { - qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate); - } else { - return Collections. emptyList(); - } - quotaUsageRecords = listBy(qb.create()); - if (quotaUsageRecords.size() == 0) { - quotaUsageRecords.addAll(lastQuotaBalanceVO(accountId, domainId, startDate)); - } - return quotaUsageRecords; + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true, 0L, Long.MAX_VALUE); + QueryBuilder qb = getQuotaBalanceQueryBuilder(accountId, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.GT, 0); + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.BETWEEN, startDate, endDate); - } + return search(qb.create(), filter); }); - } @Override - public List lastQuotaBalanceVO(final Long accountId, final Long domainId, final Date pivotDate) { - return Transaction.execute(TransactionLegacy.USAGE_DB, new TransactionCallback>() { - @Override - public List doInTransaction(final TransactionStatus status) { - List quotaUsageRecords = null; - List trimmedRecords = new ArrayList(); - Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", false, 0L, 100L); - // ASSUMPTION there will be less than 100 continuous credit - // transactions - QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); - if (accountId != null) { - qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); - } - if (domainId != null) { - qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); - } - if ((pivotDate != null)) { - qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ, pivotDate); - } - quotaUsageRecords = search(qb.create(), filter); - - // get records before startDate to find start balance - for (QuotaBalanceVO entry : quotaUsageRecords) { - if (logger.isDebugEnabled()) { - logger.debug("FindQuotaBalance Entry=" + entry); - } - if (entry.getCreditsId() > 0) { - trimmedRecords.add(entry); - } else { - trimmedRecords.add(entry); - break; // add only consecutive credit entries and last balance entry - } - } - return trimmedRecords; + public List listQuotaBalances(final Long accountId, final Long domainId, final Date startDate, final Date endDate) { + return Transaction.execute(TransactionLegacy.USAGE_DB, (TransactionCallback>) status -> { + QueryBuilder qb = getQuotaBalanceQueryBuilder(accountId, domainId); + qb.and(qb.entity().getCreditsId(), SearchCriteria.Op.EQ, 0); + if (startDate != null) { + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.GTEQ, startDate); + } + if (endDate != null) { + qb.and(qb.entity().getUpdatedOn(), SearchCriteria.Op.LTEQ, endDate); } + + Filter filter = new Filter(QuotaBalanceVO.class, "updatedOn", true); + return listBy(qb.create(), filter); }); } @Override - public BigDecimal lastQuotaBalance(final Long accountId, final Long domainId, Date startDate) { - List quotaBalance = lastQuotaBalanceVO(accountId, domainId, startDate); - BigDecimal finalBalance = new BigDecimal(0); - if (quotaBalance.isEmpty()) { - logger.info("There are no balance entries on or before the requested date."); - return finalBalance; + public BigDecimal getLastQuotaBalance(final Long accountId, final Long domainId) { + QuotaBalanceVO quotaBalance = getLastQuotaBalanceEntry(accountId, domainId, null); + BigDecimal finalBalance = BigDecimal.ZERO; + Date startDate = DateUtils.addDays(new Date(), -1); + if (quotaBalance == null) { + logger.info("There are no balance entries for account [{}] and domain [{}]. Considering only new added credits.", accountId, domainId); + } else { + finalBalance = quotaBalance.getCreditBalance(); + startDate = quotaBalance.getUpdatedOn(); } - for (QuotaBalanceVO entry : quotaBalance) { - if (logger.isDebugEnabled()) { - logger.debug("lastQuotaBalance Entry=" + entry); - } - finalBalance = finalBalance.add(entry.getCreditBalance()); + + List credits = findCreditBalances(accountId, domainId, startDate, new Date()); + + for (QuotaBalanceVO credit : credits) { + finalBalance = finalBalance.add(credit.getCreditBalance()); } + return finalBalance; } + private QueryBuilder getQuotaBalanceQueryBuilder(Long accountId, Long domainId) { + QueryBuilder qb = QueryBuilder.create(QuotaBalanceVO.class); + qb.and(qb.entity().getAccountId(), SearchCriteria.Op.EQ, accountId); + qb.and(qb.entity().getDomainId(), SearchCriteria.Op.EQ, domainId); + return qb; + } + } diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java index fb093a456790..bcdbc3b46cec 100644 --- a/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/activationrule/presetvariables/PresetVariableHelperTest.java @@ -31,11 +31,13 @@ import com.cloud.dc.ClusterDetailsVO; import com.cloud.host.HostTagVO; import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.Network; import com.cloud.network.dao.NetworkVO; import com.cloud.network.vpc.VpcOfferingVO; import com.cloud.network.vpc.VpcVO; import com.cloud.network.vpc.dao.VpcOfferingDao; import com.cloud.storage.StoragePoolTagVO; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.RoleVO; import org.apache.cloudstack.acl.dao.RoleDao; @@ -238,6 +240,8 @@ private Value getValueForTests() { value.setVmSnapshotType(VMSnapshot.Type.Disk.toString()); value.setComputingResources(getComputingResourcesForTests()); value.setVolumeType(Volume.Type.DATADISK.toString()); + value.setState(Network.State.Implemented.toString()); + value.setResourceCounting(getResourceCountingForTests()); value.setNetworkOffering(getNetworkOfferingForTests()); value.setVpcOffering(getVpcOfferingForTests()); return value; @@ -276,6 +280,13 @@ private Configuration getConfigurationForTests() { return configuration; } + private ResourceCounting getResourceCountingForTests() { + ResourceCounting resourceCounting = new ResourceCounting(); + resourceCounting.setRunningVms(1); + resourceCounting.setStoppedVms(1); + return resourceCounting; + } + private List getHostTagsForTests() { return Arrays.asList(new HostTagVO(1, "tag1", false), new HostTagVO(1, "tag2", false)); } @@ -1343,8 +1354,8 @@ public void loadPresetVariableValueForNetworkTestRecordIsNetworkSetFields() { Mockito.doReturn(expected.getId()).when(networkVoMock).getUuid(); Mockito.doReturn(expected.getName()).when(networkVoMock).getName(); Mockito.doReturn(expected.getState()).when(usageVoMock).getState(); + Mockito.doReturn(expected.getResourceCounting()).when(presetVariableHelperSpy).getPresetVariableValueNetworkResourceCounting(Mockito.anyLong()); Mockito.doReturn(expected.getNetworkOffering()).when(presetVariableHelperSpy).getPresetVariableValueNetworkOffering(Mockito.anyLong()); - Mockito.doReturn(UsageTypes.NETWORK).when(usageVoMock).getUsageType(); Value result = new Value(); @@ -1352,7 +1363,27 @@ public void loadPresetVariableValueForNetworkTestRecordIsNetworkSetFields() { assertPresetVariableIdAndName(expected, result); Assert.assertEquals(expected.getState(), result.getState()); - Assert.assertEquals(expected.getNetworkOffering(), result.getNetworkOffering()); + Assert.assertEquals(expected.getResourceCounting(), result.getResourceCounting()); + } + + @Test + public void getPresetVariableValueNetworkResourceCountingTestSetValueAndReturnObject() { + VMInstanceVO vmInstanceVoMock1 = Mockito.spy(VMInstanceVO.class); + vmInstanceVoMock1.setState(VirtualMachine.State.Stopped); + + VMInstanceVO vmInstanceVoMock2 = Mockito.spy(VMInstanceVO.class); + vmInstanceVoMock2.setState(VirtualMachine.State.Running); + + Mockito.doReturn(List.of(vmInstanceVoMock1, vmInstanceVoMock2)).when(vmInstanceDaoMock).listNonRemovedVmsByTypeAndNetwork(Mockito.anyLong(), Mockito.any()); + + mockMethodValidateIfObjectIsNull(); + + ResourceCounting expected = getResourceCountingForTests(); + + ResourceCounting result = presetVariableHelperSpy.getPresetVariableValueNetworkResourceCounting(1L); + + Assert.assertEquals(expected.getRunningVms(), result.getRunningVms()); + Assert.assertEquals(expected.getStoppedVms(), result.getStoppedVms()); } @Test @@ -1362,7 +1393,7 @@ public void loadPresetVariableValueForVpcTestRecordIsNotAVpcDoNothing() { presetVariableHelperSpy.loadPresetVariableValueForVpc(usageVoMock, null); }); - Mockito.verifyNoInteractions(networkDaoMock); + Mockito.verifyNoInteractions(vpcDaoMock); } @Test diff --git a/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImplTest.java b/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImplTest.java new file mode 100644 index 000000000000..afdd88f8d1da --- /dev/null +++ b/framework/quota/src/test/java/org/apache/cloudstack/quota/dao/QuotaBalanceDaoImplTest.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 org.apache.cloudstack.quota.dao; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; + +import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class QuotaBalanceDaoImplTest { + QuotaBalanceDaoImpl quotaBalanceDaoImplSpy = Mockito.spy(QuotaBalanceDaoImpl.class); + + @Mock + QuotaBalanceVO quotaBalanceVoMock; + + @Test + public void getLastQuotaBalanceTestLastEntryIsNullAndNoCreditsReturnsZero() { + Mockito.doReturn(null).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(new ArrayList<>()).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any()); + + BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(1L, 2L); + + Assert.assertEquals(BigDecimal.ZERO, result); + } + + @Test + public void getLastQuotaBalanceTestReturnsLastEntryAndNoCredits() { + BigDecimal expected = BigDecimal.valueOf(-1542.46); + Mockito.doReturn(quotaBalanceVoMock).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(expected).when(quotaBalanceVoMock).getCreditBalance(); + Mockito.doReturn(new ArrayList<>()).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any()); + + BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L); + + Assert.assertEquals(expected, result); + } + + @Test + public void getLastQuotaBalanceTestReturnsLastEntryPlusCredits() { + BigDecimal balance = BigDecimal.valueOf(-1542.46); + BigDecimal credit1 = new BigDecimal("150.14"); + BigDecimal credit2 = new BigDecimal("78.96"); + BigDecimal expected = balance.add(credit1).add(credit2); + + Mockito.doReturn(quotaBalanceVoMock).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(balance, credit1, credit2).when(quotaBalanceVoMock).getCreditBalance(); + Mockito.doReturn(Arrays.asList(quotaBalanceVoMock, quotaBalanceVoMock)).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any()); + + BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L); + + Assert.assertEquals(expected, result); + } + + @Test + public void getLastQuotaBalanceTestReturnsLastEntryIsNullPlusCredits() { + BigDecimal credit1 = new BigDecimal("150.14"); + BigDecimal credit2 = new BigDecimal("78.96"); + BigDecimal expected = credit1.add(credit2); + + Mockito.doReturn(null).when(quotaBalanceDaoImplSpy).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(credit1, credit2).when(quotaBalanceVoMock).getCreditBalance(); + Mockito.doReturn(Arrays.asList(quotaBalanceVoMock, quotaBalanceVoMock)).when(quotaBalanceDaoImplSpy).findCreditBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any()); + + BigDecimal result = quotaBalanceDaoImplSpy.getLastQuotaBalance(5L, 8L); + + Assert.assertEquals(expected, result); + } +} diff --git a/packaging/el8/cloud.spec b/packaging/el8/cloud.spec index 3dee161bf274..13c0a36cb106 100644 --- a/packaging/el8/cloud.spec +++ b/packaging/el8/cloud.spec @@ -117,7 +117,7 @@ Requires: ipset Requires: perl Requires: rsync Requires: cifs-utils -Requires: edk2-ovmf +Requires: (edk2-ovmf or qemu-ovmf-x86_64) Requires: swtpm Requires: (python3-libvirt or python3-libvirt-python) Requires: (qemu-img or qemu-tools) diff --git a/packaging/suse15 b/packaging/suse15 deleted file mode 120000 index 4dad90d45e0c..000000000000 --- a/packaging/suse15 +++ /dev/null @@ -1 +0,0 @@ -el8 \ No newline at end of file diff --git a/packaging/suse15/cloud-ipallocator.rc b/packaging/suse15/cloud-ipallocator.rc new file mode 120000 index 000000000000..647598e6dc41 --- /dev/null +++ b/packaging/suse15/cloud-ipallocator.rc @@ -0,0 +1 @@ +../el8/cloud-ipallocator.rc \ No newline at end of file diff --git a/packaging/suse15/cloud.limits b/packaging/suse15/cloud.limits new file mode 120000 index 000000000000..37be77e3acf2 --- /dev/null +++ b/packaging/suse15/cloud.limits @@ -0,0 +1 @@ +../el8/cloud.limits \ No newline at end of file diff --git a/packaging/suse15/cloud.spec b/packaging/suse15/cloud.spec new file mode 100644 index 000000000000..cdfc5a72a34e --- /dev/null +++ b/packaging/suse15/cloud.spec @@ -0,0 +1,750 @@ +# 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. + +%define __os_install_post %{nil} +%global debug_package %{nil} +%global __requires_exclude libc\\.so\\..*|libc\\.so\\.6\\(GLIBC_.*\\) +%define _binaries_in_noarch_packages_terminate_build 0 + +# DISABLE the post-percentinstall java repacking and line number stripping +# we need to find a way to just disable the java repacking and line number stripping, but not the autodeps + +Name: cloudstack +Summary: CloudStack IaaS Platform +#http://fedoraproject.org/wiki/PackageNamingGuidelines#Pre-Release_packages +%define _maventag %{_fullver} +Release: %{_rel} + +Version: %{_ver} +License: ASL 2.0 +Vendor: Apache CloudStack +Packager: Apache CloudStack +Group: System Environment/Libraries +# FIXME do groups for every single one of the subpackages +Source0: %{name}-%{_maventag}.tgz +BuildRoot: %{_tmppath}/%{name}-%{_maventag}-%{release}-build +BuildArch: noarch + +BuildRequires: (java-11-openjdk-devel or java-17-openjdk-devel or java-21-openjdk-devel) +#BuildRequires: ws-commons-util +BuildRequires: jpackage-utils +BuildRequires: gcc +BuildRequires: glibc-devel +BuildRequires: /usr/bin/mkisofs +BuildRequires: python3-setuptools +BuildRequires: wget +BuildRequires: nodejs + +%description +CloudStack is a highly-scalable elastic, open source, +intelligent IaaS cloud implementation. + +%package management +Summary: CloudStack management server UI +Requires: (java-17-openjdk or java-21-openjdk) +Requires: (tzdata-java or timezone-java) +Requires: python3 +Requires: bash +Requires: gawk +Requires: which +Requires: file +Requires: tar +Requires: bzip2 +Requires: gzip +Requires: unzip +Requires: (/sbin/mount.nfs or /usr/sbin/mount.nfs) +Requires: (openssh-clients or openssh) +Requires: (nfs-utils or nfs-client) +Requires: iproute +Requires: wget +Requires: (mysql or mariadb or mysql8.4) +Requires: sudo +Requires: /sbin/service +Requires: /sbin/chkconfig +Requires: /usr/bin/ssh-keygen +Requires: (genisoimage or mkisofs or xorrisofs) +Requires: ipmitool +Requires: %{name}-common = %{_ver} +Requires: (iptables-services or iptables) +Requires: rng-tools +Requires: (qemu-img or qemu-tools) +Requires: python3-pip +Requires: python3-six +Requires: python3-protobuf +Requires: python3-setuptools +Requires: (libgcrypt > 1.8.3 or libgcrypt20) +Group: System Environment/Libraries +%description management +The CloudStack management server is the central point of coordination, +management, and intelligence in CloudStack. + +%package common +Summary: Apache CloudStack common files and scripts +Requires: python3 +Group: System Environment/Libraries +%description common +The Apache CloudStack files shared between agent and management server +%global __requires_exclude libc\\.so\\..*|libc\\.so\\.6\\(GLIBC_.*\\)|^(libuuid\\.so\\.1|/usr/bin/python)$ + +%package agent +Summary: CloudStack Agent for KVM hypervisors +Requires: (openssh-clients or openssh) +Requires: (java-17-openjdk or java-21-openjdk) +Requires: (tzdata-java or timezone-java) +Requires: %{name}-common = %{_ver} +Requires: libvirt +Requires: libvirt-daemon-driver-storage-rbd +Requires: ebtables +Requires: iptables +Requires: ethtool +Requires: (net-tools or net-tools-deprecated) +Requires: iproute +Requires: ipset +Requires: perl +Requires: rsync +Requires: cifs-utils +Requires: (edk2-ovmf or qemu-ovmf-x86_64) +Requires: swtpm +Requires: (python3-libvirt or python3-libvirt-python) +Requires: (qemu-img or qemu-tools) +Requires: qemu-kvm +Requires: cryptsetup +Requires: rng-tools +Requires: (libgcrypt > 1.8.3 or libgcrypt20) +Requires: (selinux-tools if selinux-tools) +Requires: sysstat +Provides: cloud-agent +Group: System Environment/Libraries +%description agent +The CloudStack agent for KVM hypervisors + +%package baremetal-agent +Summary: CloudStack baremetal agent +Requires: tftp-server +Requires: xinetd +Requires: syslinux +Requires: chkconfig +Requires: dhcp +Requires: httpd +Group: System Environment/Libraries +%description baremetal-agent +The CloudStack baremetal agent + +%package usage +Summary: CloudStack Usage calculation server +Requires: (java-17-openjdk or java-21-openjdk) +Requires: (tzdata-java or timezone-java) +Group: System Environment/Libraries +%description usage +The CloudStack usage calculation service + +%package ui +Summary: CloudStack UI +Group: System Environment/Libraries +%description ui +The CloudStack UI + +%package marvin +Summary: Apache CloudStack Marvin library +Requires: python3-pip +Requires: gcc +Requires: python3-devel +Requires: libffi-devel +Requires: openssl-devel +Group: System Environment/Libraries +%description marvin +Apache CloudStack Marvin library + +%package integration-tests +Summary: Apache CloudStack Marvin integration tests +Requires: %{name}-marvin = %{_ver} +Group: System Environment/Libraries +%description integration-tests +Apache CloudStack Marvin integration tests + +%if "%{_ossnoss}" == "noredist" +%package mysql-ha +Summary: Apache CloudStack Balancing Strategy for MySQL +Group: System Environmnet/Libraries +%description mysql-ha +Apache CloudStack Balancing Strategy for MySQL + +%endif + +%prep +echo Doing CloudStack build + +%setup -q -n %{name}-%{_maventag} + +%build + +cp packaging/suse15/replace.properties build/replace.properties +echo VERSION=%{_maventag} >> build/replace.properties +echo PACKAGE=%{name} >> build/replace.properties +touch build/gitrev.txt +echo $(git rev-parse HEAD) > build/gitrev.txt + +if [ "%{_ossnoss}" == "NOREDIST" -o "%{_ossnoss}" == "noredist" ] ; then + echo "Adding noredist flag to the maven build" + FLAGS="$FLAGS -Dnoredist" +fi + +if [ "%{_sim}" == "SIMULATOR" -o "%{_sim}" == "simulator" ] ; then + echo "Adding simulator flag to the maven build" + FLAGS="$FLAGS -Dsimulator" +fi + +if [ \"%{_temp}\" != "" ]; then + echo "Adding flags to package requested templates" + FLAGS="$FLAGS `rpm --eval %{?_temp}`" +fi + +mvn -Psystemvm,developer $FLAGS clean package +cd ui && npm install && npm run build && cd .. + +%install +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} +# Common directories +mkdir -p ${RPM_BUILD_ROOT}%{_bindir} +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/ipallocator +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management +mkdir -p ${RPM_BUILD_ROOT}%{_initrddir} +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/default +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d + +# Common +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site +mkdir -p ${RPM_BUILD_ROOT}/usr/bin +cp -r scripts/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts +install -D systemvm/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/vms/ +install python/lib/cloud_utils.py ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py +cp -r python/lib/cloudutils ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/ +python3 -m py_compile ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloud_utils.py +python3 -m compileall ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/python-site/cloudutils +cp build/gitrev.txt ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts +cp packaging/suse15/cloudstack-sccs ${RPM_BUILD_ROOT}/usr/bin + +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco +cp -r plugins/network-elements/cisco-vnmc/src/main/scripts/network/cisco/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/scripts/network/cisco + +# Management +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/ +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/run +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel + +# Setup Jetty +ln -sf /etc/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/conf +ln -sf /var/log/%{name}/management ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/logs + +install -D client/target/utilities/bin/cloud-migrate-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-migrate-databases +install -D client/target/utilities/bin/cloud-set-guest-password ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-password +install -D client/target/utilities/bin/cloud-set-guest-sshkey ${RPM_BUILD_ROOT}%{_bindir}/%{name}-set-guest-sshkey +install -D client/target/utilities/bin/cloud-setup-databases ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-databases +install -D client/target/utilities/bin/cloud-setup-encryption ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-encryption +install -D client/target/utilities/bin/cloud-setup-management ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-management +install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-baremetal +install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm +install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses +# Bundle cmk in cloudstack-management +wget https://github.com/apache/cloudstack-cloudmonkey/releases/latest/download/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk +chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk + +cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup +cp -r plugins/integrations/kubernetes-service/src/main/resources/conf/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf +cp -r client/target/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/ +cp -r client/target/classes/META-INF/webapp ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp +cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/ +cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/ +rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json +ln -sf /etc/%{name}/management/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapp/config.json +mv ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cloud-client-ui-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/cloudstack-%{_maventag}.jar +cp client/target/lib/*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/lib/ + +# Don't package the scripts in the management webapp +rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/scripts +rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/vms + +for name in db.properties server.properties log4j-cloud.xml environment.properties java.security.ciphers +do + cp client/target/conf/$name ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/$name +done + +ln -sf log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/management/log4j2.xml + +install python/bindir/cloud-external-ipallocator.py ${RPM_BUILD_ROOT}%{_bindir}/%{name}-external-ipallocator.py +install -D client/target/pythonlibs/jasypt-1.9.3.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar +install -D utils/target/cloud-utils-%{_maventag}-bundled.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-common/lib/%{name}-utils.jar + +install -D packaging/suse15/cloud-ipallocator.rc ${RPM_BUILD_ROOT}%{_initrddir}/%{name}-ipallocator +install -D packaging/suse15/cloud.limits ${RPM_BUILD_ROOT}%{_sysconfdir}/security/limits.d/cloud +install -D packaging/suse15/filelimit.conf ${RPM_BUILD_ROOT}%{_sysconfdir}/systemd/system/%{name}-management.service.d +install -D packaging/systemd/cloudstack-management.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-management.service +install -D packaging/systemd/cloudstack-management.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-management +install -D server/target/conf/cloudstack-sudoers ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management +touch ${RPM_BUILD_ROOT}%{_localstatedir}/run/%{name}-management.pid +#install -D server/target/conf/cloudstack-catalina.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-catalina +install -D server/target/conf/cloudstack-management.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-management + +install -D plugins/integrations/kubernetes-service/src/main/resources/conf/etcd-node.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/etcd-node.yml +install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/k8s-control-node.yml +install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/k8s-control-node-add.yml +install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/cks/conf/k8s-node.yml + +# SystemVM template +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm +cp -r engine/schema/dist/systemvm-templates/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm +rm -rf ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/templates/systemvm/sha512sum.txt + +# Sample Extensions +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/extensions +cp -r extensions/* ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/extensions +ln -sf %{_sysconfdir}/%{name}/extensions ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/extensions + +# UI +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/ +cp -r client/target/classes/META-INF/webapp/WEB-INF ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui +cp ui/dist/config.json ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/ui/ +cp -r ui/dist/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/ +rm -f ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json +ln -sf /etc/%{name}/ui/config.json ${RPM_BUILD_ROOT}%{_datadir}/%{name}-ui/config.json + +# Package mysql-connector-python (bundled to avoid dependency on external community repo) +# Version 8.0.31 is the last version supporting Python 3.6 (EL8) +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl +# Version 8.3.0 supports Python 3.8 to 3.12 (EL9, EL10) +wget -P ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup/wheel https://files.pythonhosted.org/packages/53/ed/26a4b8cacb8852c6fd97d2d58a7f2591c41989807ea82bd8d9725a4e6937/mysql_connector_python-8.3.0-py2.py3-none-any.whl + +chmod 440 ${RPM_BUILD_ROOT}%{_sysconfdir}/sudoers.d/%{name}-management +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/mnt +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/%{name}/management +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/work +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/cache/%{name}/management/temp +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/management +chmod 770 ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent + +# KVM Agent +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/agent +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/plugins +install -D packaging/systemd/cloudstack-agent.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-agent.service +install -D packaging/systemd/cloudstack-rolling-maintenance@.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-rolling-maintenance@.service +install -D packaging/systemd/cloudstack-agent.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-agent +install -D agent/target/transformed/agent.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/agent.properties +install -D agent/target/transformed/uefi.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/uefi.properties +install -D agent/target/transformed/environment.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/environment.properties +install -D agent/target/transformed/log4j-cloud.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/agent/log4j-cloud.xml +install -D agent/target/transformed/cloud-setup-agent ${RPM_BUILD_ROOT}%{_bindir}/%{name}-setup-agent +install -D agent/target/transformed/cloudstack-agent-upgrade ${RPM_BUILD_ROOT}%{_bindir}/%{name}-agent-upgrade +install -D agent/target/transformed/cloud-guest-tool ${RPM_BUILD_ROOT}%{_bindir}/%{name}-guest-tool +install -D agent/target/transformed/libvirtqemuhook ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook +install -D agent/target/transformed/rolling-maintenance ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/rolling-maintenance +install -D agent/target/transformed/cloud-ssh ${RPM_BUILD_ROOT}%{_bindir}/%{name}-ssh +install -D agent/target/transformed/cloudstack-agent-profile.sh ${RPM_BUILD_ROOT}%{_sysconfdir}/profile.d/%{name}-agent-profile.sh +install -D agent/target/transformed/cloudstack-agent.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-agent +install -D plugins/hypervisors/kvm/target/cloud-plugin-hypervisor-kvm-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%name-agent/lib/cloud-plugin-hypervisor-kvm-%{_maventag}.jar +cp plugins/hypervisors/kvm/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib +cp plugins/storage/volume/storpool/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib +cp plugins/storage/volume/linstor/target/*.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib + +# Usage server +mkdir -p ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib +install -D usage/target/cloud-usage-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/cloud-usage-%{_maventag}.jar +install -D usage/target/transformed/db.properties ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/db.properties +install -D usage/target/transformed/log4j-cloud_usage.xml ${RPM_BUILD_ROOT}%{_sysconfdir}/%{name}/usage/log4j-cloud.xml +cp usage/target/dependencies/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/ +cp client/target/lib/mysql*jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-usage/lib/ +install -D packaging/systemd/cloudstack-usage.service ${RPM_BUILD_ROOT}%{_unitdir}/%{name}-usage.service +install -D packaging/systemd/cloudstack-usage.default ${RPM_BUILD_ROOT}%{_sysconfdir}/default/%{name}-usage +mkdir -p ${RPM_BUILD_ROOT}%{_localstatedir}/log/%{name}/usage/ +install -D usage/target/transformed/cloudstack-usage.logrotate ${RPM_BUILD_ROOT}%{_sysconfdir}/logrotate.d/%{name}-usage + +# Marvin +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin +cp tools/marvin/dist/Marvin-*.tar.gz ${RPM_BUILD_ROOT}%{_datadir}/%{name}-marvin/ + +# integration-tests +mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests +cp -r test/integration/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-integration-tests/ + +# MYSQL HA +if [ "x%{_ossnoss}" == "xnoredist" ] ; then + mkdir -p ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib + cp -r plugins/database/mysql-ha/target/cloud-plugin-database-mysqlha-%{_maventag}.jar ${RPM_BUILD_ROOT}%{_datadir}/%{name}-mysql-ha/lib +fi + +#License files from whisker +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-management-%{version}/LICENSE +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-common-%{version}/LICENSE +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE +install -D tools/whisker/NOTICE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE +install -D tools/whisker/LICENSE ${RPM_BUILD_ROOT}%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE + +%clean +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} + +%posttrans common + +unalias cp +python_dir=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))") +if [ ! -z $python_dir ];then + cp -f -r /usr/share/cloudstack-common/python-site/* $python_dir/ +fi + +%preun management +/usr/bin/systemctl stop cloudstack-management || true +/usr/bin/systemctl disable cloudstack-management || true + +%pre management +id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \ + -r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud || true + +rm -rf %{_localstatedir}/cache/cloudstack + +# in case of upgrade to 4.9+ copy commands.properties if not exists in /etc/cloudstack/management/ +if [ "$1" == "2" ] ; then + if [ -f "%{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties" ] && [ ! -f "%{_sysconfdir}/%{name}/management/commands.properties" ] ; then + cp -p %{_datadir}/%{name}-management/webapps/client/WEB-INF/classes/commands.properties %{_sysconfdir}/%{name}/management/commands.properties + fi +fi + +# Remove old tomcat symlinks and env config file +if [ -L "%{_datadir}/%{name}-management/lib" ] +then + rm -f %{_datadir}/%{name}-management/bin + rm -f %{_datadir}/%{name}-management/lib + rm -f %{_datadir}/%{name}-management/temp + rm -f %{_datadir}/%{name}-management/work + rm -f %{_sysconfdir}/default/%{name}-management +fi + +%post management +# Install mysql-connector-python wheel +# Detect Python version to install compatible wheel +if python3 -c 'import sys; sys.exit(0 if sys.version_info >= (3, 7) else 1)'; then + pip3 install %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.3.0-py2.py3-none-any.whl +else + pip3 install %{_datadir}/%{name}-management/setup/wheel/mysql_connector_python-8.0.31-py2.py3-none-any.whl +fi + +/usr/bin/systemctl enable cloudstack-management > /dev/null 2>&1 || true +/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true + +grep -s -q "db.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.cloud.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "db.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.usage.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "db.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" || sed -i -e "\$adb.simulator.driver=jdbc:mysql" "%{_sysconfdir}/%{name}/management/db.properties" + +# Update DB properties having master and slave(s), with source and replica(s) respectively (for inclusiveness) +grep -s -q "^db.cloud.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.slaves=/db.cloud.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "^db.cloud.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.secondsBeforeRetryMaster=/db.cloud.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "^db.cloud.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.cloud.queriesBeforeRetryMaster=/db.cloud.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "^db.usage.slaves=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.slaves=/db.usage.replicas=/g" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "^db.usage.secondsBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.secondsBeforeRetryMaster=/db.usage.secondsBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" +grep -s -q "^db.usage.queriesBeforeRetryMaster=" "%{_sysconfdir}/%{name}/management/db.properties" && sed -i "s/^db.usage.queriesBeforeRetryMaster=/db.usage.queriesBeforeRetrySource=/g" "%{_sysconfdir}/%{name}/management/db.properties" + +if [ ! -f %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/vhd-util ] ; then + echo Please download vhd-util from http://download.cloudstack.org/tools/vhd-util and put it in + echo %{_datadir}/cloudstack-common/scripts/vm/hypervisor/xenserver/ +fi + +if [ -f %{_sysconfdir}/sysconfig/%{name}-management ] ; then + rm -f %{_sysconfdir}/sysconfig/%{name}-management +fi + +chown -R cloud:cloud /var/log/cloudstack/management +chown -R cloud:cloud /usr/share/cloudstack-management/templates +find /usr/share/cloudstack-management/templates -type d -exec chmod 0770 {} \; + +systemctl daemon-reload + +%posttrans management +# Print help message +if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then + sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text + /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text management +fi + +%preun agent +/sbin/service cloudstack-agent stop || true +if [ "$1" == "0" ] ; then + /sbin/chkconfig --del cloudstack-agent > /dev/null 2>&1 || true +fi + +%pre agent + +# save old configs if they exist (for upgrade). Otherwise we may lose them +# when the old packages are erased. There are a lot of properties files here. +if [ -d "%{_sysconfdir}/cloud" ] ; then + mv %{_sysconfdir}/cloud %{_sysconfdir}/cloud.rpmsave +fi + +%posttrans agent + +if [ "$1" == "2" ] ; then + echo "Running %{_bindir}/%{name}-agent-upgrade to update bridge name for upgrade from CloudStack 4.0.x (and before) to CloudStack 4.1 (and later)" + %{_bindir}/%{name}-agent-upgrade +fi +if [ ! -d %{_sysconfdir}/libvirt/hooks ] ; then + mkdir %{_sysconfdir}/libvirt/hooks +fi +cp -a ${RPM_BUILD_ROOT}%{_datadir}/%{name}-agent/lib/libvirtqemuhook %{_sysconfdir}/libvirt/hooks/qemu +mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp +/usr/bin/systemctl restart libvirtd +/usr/bin/systemctl enable cloudstack-agent > /dev/null 2>&1 || true +/usr/bin/systemctl enable cloudstack-rolling-maintenance@p > /dev/null 2>&1 || true +/usr/bin/systemctl enable --now rngd > /dev/null 2>&1 || true + +# if saved agent.properties from upgrade exist, copy them over +if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/agent.properties" ]; then + mv %{_sysconfdir}/%{name}/agent/agent.properties %{_sysconfdir}/%{name}/agent/agent.properties.rpmnew + cp -p %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/%{name}/agent + # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall + mv %{_sysconfdir}/cloud.rpmsave/agent/agent.properties %{_sysconfdir}/cloud.rpmsave/agent/agent.properties.rpmsave +fi + +# if saved uefi.properties from upgrade exist, copy them over +if [ -f "%{_sysconfdir}/cloud.rpmsave/agent/uefi.properties" ]; then + mv %{_sysconfdir}/%{name}/agent/uefi.properties %{_sysconfdir}/%{name}/agent/uefi.properties.rpmnew + cp -p %{_sysconfdir}/cloud.rpmsave/agent/uefi.properties %{_sysconfdir}/%{name}/agent + # make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall + mv %{_sysconfdir}/cloud.rpmsave/agent/uefi.properties %{_sysconfdir}/cloud.rpmsave/agent/uefi.properties.rpmsave +fi + +systemctl daemon-reload + +# Print help message +if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then + sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text + /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text agent +fi + +%pre usage +id cloud > /dev/null 2>&1 || /usr/sbin/useradd -M -U -c "CloudStack unprivileged user" \ + -r -s /bin/sh -d %{_localstatedir}/cloudstack/management cloud|| true + +%preun usage +/sbin/service cloudstack-usage stop || true +if [ "$1" == "0" ] ; then + /sbin/chkconfig --del cloudstack-usage > /dev/null 2>&1 || true +fi + +%post usage +if [ -f "%{_sysconfdir}/%{name}/management/db.properties" ]; then + echo "Replacing usage server's db.properties with a link to the management server's db.properties" + rm -f %{_sysconfdir}/%{name}/usage/db.properties + ln -s %{_sysconfdir}/%{name}/management/db.properties %{_sysconfdir}/%{name}/usage/db.properties + /usr/bin/systemctl enable cloudstack-usage > /dev/null 2>&1 || true +fi + +if [ -f "%{_sysconfdir}/%{name}/management/key" ]; then + echo "Replacing usage server's key with a link to the management server's key" + rm -f %{_sysconfdir}/%{name}/usage/key + ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key +fi + +if [ ! -f "%{_sysconfdir}/%{name}/usage/key" ]; then + ln -s %{_sysconfdir}/%{name}/management/key %{_sysconfdir}/%{name}/usage/key +fi + +mkdir -p /usr/local/libexec +if [ ! -f "/usr/local/libexec/sanity-check-last-id" ]; then + echo 1 > /usr/local/libexec/sanity-check-last-id +fi +chown cloud:cloud /usr/local/libexec/sanity-check-last-id + +%posttrans usage +# Print help message +if [ -f "/usr/share/cloudstack-common/scripts/installer/cloudstack-help-text" ];then + sed -i "s,^ACS_VERSION=.*,ACS_VERSION=%{_maventag},g" /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text + /usr/share/cloudstack-common/scripts/installer/cloudstack-help-text usage +fi + +%post marvin +pip3 install --upgrade https://files.pythonhosted.org/packages/08/1f/42d74bae9dd6dcfec67c9ed0f3fa482b1ae5ac5f117ca82ab589ecb3ca19/mysql_connector_python-8.0.31-py2.py3-none-any.whl +pip3 install --upgrade /usr/share/cloudstack-marvin/Marvin-*.tar.gz + +#No default permission as the permission setup is complex +%files management +%defattr(-,root,root,-) +%dir %{_datadir}/%{name}-management +%dir %attr(0770,root,cloud) %{_localstatedir}/%{name}/mnt +%dir %attr(0770,cloud,cloud) %{_localstatedir}/%{name}/management +%dir %attr(0770,root,cloud) %{_localstatedir}/cache/%{name}/management +%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/management +%config(noreplace) %{_sysconfdir}/default/%{name}-management +%config(noreplace) %{_sysconfdir}/sudoers.d/%{name}-management +%config(noreplace) %{_sysconfdir}/security/limits.d/cloud +%config(noreplace) %{_sysconfdir}/systemd/system/%{name}-management.service.d +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/db.properties +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/server.properties +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/management/config.json +%config(noreplace) %{_sysconfdir}/%{name}/management/log4j-cloud.xml +%config(noreplace) %{_sysconfdir}/%{name}/management/log4j2.xml +%config(noreplace) %{_sysconfdir}/%{name}/management/environment.properties +%config(noreplace) %{_sysconfdir}/%{name}/management/java.security.ciphers +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-management +%attr(0644,root,root) %{_unitdir}/%{name}-management.service +%attr(0755,cloud,cloud) %{_localstatedir}/run/%{name}-management.pid +%attr(0755,root,root) %{_bindir}/%{name}-setup-management +%attr(0755,root,root) %{_bindir}/%{name}-update-xenserver-licenses +%{_datadir}/%{name}-management/conf +%{_datadir}/%{name}-management/lib/*.jar +%{_datadir}/%{name}-management/logs +%{_datadir}/%{name}-management/templates +%{_datadir}/%{name}-management/extensions +%attr(0755,root,root) %{_bindir}/%{name}-setup-databases +%attr(0755,root,root) %{_bindir}/%{name}-migrate-databases +%attr(0755,root,root) %{_bindir}/%{name}-set-guest-password +%attr(0755,root,root) %{_bindir}/%{name}-set-guest-sshkey +%attr(0755,root,root) %{_bindir}/%{name}-sysvmadm +%attr(0755,root,root) %{_bindir}/%{name}-setup-encryption +%attr(0755,root,root) %{_bindir}/cmk +%{_datadir}/%{name}-management/cks/conf/*.yml +%{_datadir}/%{name}-management/setup/*.sql +%{_datadir}/%{name}-management/setup/*.sh +%{_datadir}/%{name}-management/setup/server-setup.xml +%{_datadir}/%{name}-management/webapp/* +%dir %attr(0770, cloud, cloud) %{_datadir}/%{name}-management/templates +%dir %attr(0770, cloud, cloud) %{_datadir}/%{name}-management/templates/systemvm +%attr(0644, cloud, cloud) %{_datadir}/%{name}-management/templates/systemvm/* +%attr(0755,root,root) %{_bindir}/%{name}-external-ipallocator.py +%attr(0755,root,root) %{_initrddir}/%{name}-ipallocator +%dir %attr(0770,root,root) %{_localstatedir}/log/%{name}/ipallocator +%{_defaultdocdir}/%{name}-management-%{version}/LICENSE +%{_defaultdocdir}/%{name}-management-%{version}/NOTICE +%{_datadir}/%{name}-management/setup/wheel/*.whl +%dir %attr(0755,cloud,cloud) %{_sysconfdir}/%{name}/extensions +%attr(0755,cloud,cloud) %{_sysconfdir}/%{name}/extensions/* + +%files agent +%attr(0755,root,root) %{_bindir}/%{name}-setup-agent +%attr(0755,root,root) %{_bindir}/%{name}-agent-upgrade +%attr(0755,root,root) %{_bindir}/%{name}-guest-tool +%attr(0755,root,root) %{_bindir}/%{name}-ssh +%attr(0644,root,root) %{_unitdir}/%{name}-agent.service +%attr(0644,root,root) %{_unitdir}/%{name}-rolling-maintenance@.service +%config(noreplace) %{_sysconfdir}/default/%{name}-agent +%attr(0644,root,root) %{_sysconfdir}/profile.d/%{name}-agent-profile.sh +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-agent +%attr(0755,root,root) %{_datadir}/%{name}-common/scripts/network/cisco +%config(noreplace) %{_sysconfdir}/%{name}/agent +%dir %{_localstatedir}/log/%{name}/agent +%attr(0644,root,root) %{_datadir}/%{name}-agent/lib/*.jar +%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/libvirtqemuhook +%attr(0755,root,root) %{_datadir}/%{name}-agent/lib/rolling-maintenance +%dir %{_datadir}/%{name}-agent/plugins +%{_defaultdocdir}/%{name}-agent-%{version}/LICENSE +%{_defaultdocdir}/%{name}-agent-%{version}/NOTICE + +%files common +%dir %attr(0755,root,root) %{_datadir}/%{name}-common/python-site/cloudutils +%dir %attr(0755,root,root) %{_datadir}/%{name}-common/vms +%attr(0755,root,root) %{_datadir}/%{name}-common/scripts +%attr(0755,root,root) /usr/bin/cloudstack-sccs +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/agent.zip +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/cloud-scripts.tgz +%attr(0644, root, root) %{_datadir}/%{name}-common/vms/patch-sysvms.sh +%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/cloud_utils.py +%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/__pycache__/* +%attr(0644,root,root) %{_datadir}/%{name}-common/python-site/cloudutils/* +%attr(0644, root, root) %{_datadir}/%{name}-common/lib/jasypt-1.9.3.jar +%attr(0644, root, root) %{_datadir}/%{name}-common/lib/%{name}-utils.jar +%{_defaultdocdir}/%{name}-common-%{version}/LICENSE +%{_defaultdocdir}/%{name}-common-%{version}/NOTICE + +%files ui +%config(noreplace) %attr(0640,root,cloud) %{_sysconfdir}/%{name}/ui/config.json +%{_datadir}/%{name}-ui/* +%{_defaultdocdir}/%{name}-ui-%{version}/LICENSE +%{_defaultdocdir}/%{name}-ui-%{version}/NOTICE + +%files usage +%attr(0644,root,root) %{_unitdir}/%{name}-usage.service +%config(noreplace) %{_sysconfdir}/default/%{name}-usage +%config(noreplace) %attr(0644,root,root) %{_sysconfdir}/logrotate.d/%{name}-usage +%attr(0644,root,root) %{_datadir}/%{name}-usage/*.jar +%attr(0644,root,root) %{_datadir}/%{name}-usage/lib/*.jar +%dir %attr(0770,root,cloud) %{_localstatedir}/log/%{name}/usage +%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/db.properties +%attr(0644,root,root) %{_sysconfdir}/%{name}/usage/log4j-cloud.xml +%{_defaultdocdir}/%{name}-usage-%{version}/LICENSE +%{_defaultdocdir}/%{name}-usage-%{version}/NOTICE + +%files marvin +%attr(0644,root,root) %{_datadir}/%{name}-marvin/Marvin*.tar.gz +%{_defaultdocdir}/%{name}-marvin-%{version}/LICENSE +%{_defaultdocdir}/%{name}-marvin-%{version}/NOTICE + +%files integration-tests +%attr(0755,root,root) %{_datadir}/%{name}-integration-tests/* +%{_defaultdocdir}/%{name}-integration-tests-%{version}/LICENSE +%{_defaultdocdir}/%{name}-integration-tests-%{version}/NOTICE + +%if "%{_ossnoss}" == "noredist" +%files mysql-ha +%defattr(0644,cloud,cloud,0755) +%attr(0644,root,root) %{_datadir}/%{name}-mysql-ha/lib/* +%endif + +%files baremetal-agent +%attr(0755,root,root) %{_bindir}/cloudstack-setup-baremetal + +%changelog +* Thu Dec 22 2022 Rohit Yadav 4.18.0 +- Add support for EL9 + +* Fri Oct 14 2022 Daan Hoogland 4.18.0 +- initialising sanity check pointer file + +* Tue Jun 29 2021 David Jumani 4.16.0 +- Adding SUSE 15 support + +* Thu Apr 30 2015 Rohit Yadav 4.6.0 +- Remove awsapi package + +* Wed Nov 19 2014 Hugo Trippaers 4.6.0 +- Create a specific spec for CentOS 7 + +* Fri Jul 4 2014 Hugo Trippaers 4.5.0 +- Add a package for the mysql ha module + +* Fri Oct 5 2012 Hugo Trippaers 4.1.0 +- new style spec file diff --git a/packaging/suse15/cloudstack-agent.te b/packaging/suse15/cloudstack-agent.te new file mode 120000 index 000000000000..30e123f6cba5 --- /dev/null +++ b/packaging/suse15/cloudstack-agent.te @@ -0,0 +1 @@ +../el8/cloudstack-agent.te \ No newline at end of file diff --git a/packaging/suse15/cloudstack-sccs b/packaging/suse15/cloudstack-sccs new file mode 120000 index 000000000000..b9e6ed9dc085 --- /dev/null +++ b/packaging/suse15/cloudstack-sccs @@ -0,0 +1 @@ +../el8/cloudstack-sccs \ No newline at end of file diff --git a/packaging/suse15/filelimit.conf b/packaging/suse15/filelimit.conf new file mode 120000 index 000000000000..c71688ea640a --- /dev/null +++ b/packaging/suse15/filelimit.conf @@ -0,0 +1 @@ +../el8/filelimit.conf \ No newline at end of file diff --git a/packaging/suse15/replace.properties b/packaging/suse15/replace.properties new file mode 100644 index 000000000000..b1900af83406 --- /dev/null +++ b/packaging/suse15/replace.properties @@ -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. + +DBUSER=cloud +DBPW=cloud +DBROOTPW= +MSLOG=vmops.log +APISERVERLOG=api.log +DBHOST=localhost +DBDRIVER=jdbc:mysql +COMPONENTS-SPEC=components-premium.xml +REMOTEHOST=localhost +AGENTCLASSPATH= +AGENTLOG=/var/log/cloudstack/agent/agent.log +AGENTLOGDIR=/var/log/cloudstack/agent/ +AGENTSYSCONFDIR=/etc/cloudstack/agent +APISERVERLOG=/var/log/cloudstack/management/apilog.log +BINDIR=/usr/bin +COMMONLIBDIR=/usr/share/cloudstack-common +CONFIGUREVARS= +DEPSCLASSPATH= +DOCDIR= +IPALOCATORLOG=/var/log/cloudstack/management/ipallocator.log +JAVADIR=/usr/share/java +LIBEXECDIR=/usr/libexec +LOCKDIR=/var/lock +MSCLASSPATH= +MSCONF=/etc/cloudstack/management +MSENVIRON=/usr/share/cloudstack-management +MSLOG=/var/log/cloudstack/management/management-server.log +MSLOGDIR=/var/log/cloudstack/management/ +MSMNTDIR=/var/cloudstack/mnt +MSUSER=cloud +PIDDIR=/var/run +PLUGINJAVADIR=/usr/share/cloudstack-management/plugin +PREMIUMJAVADIR=/usr/share/cloudstack-management/premium +PYTHONDIR=/usr/share/cloudstack-common/python-site/ +SERVERSYSCONFDIR=/etc/sysconfig +SETUPDATADIR=/usr/share/cloudstack-management/setup +SYSCONFDIR=/etc/sysconfig +SYSTEMCLASSPATH= +SYSTEMJARS= +USAGECLASSPATH= +USAGELOG=/var/log/cloudstack/usage/usage.log +USAGESYSCONFDIR=/etc/sysconfig +EXTENSIONSDEPLOYMENTMODE=production +GUESTNVRAMTEMPLATELEGACY=/usr/share/qemu/ovmf-x86_64-vars.bin +GUESTLOADERLEGACY=/usr/share/qemu/ovmf-x86_64-code.bin +GUESTNVRAMTEMPLATESECURE=/usr/share/qemu/ovmf-x86_64-ms-vars.bin +GUESTLOADERSECURE=/usr/share/qemu/ovmf-x86_64-ms-code.bin +GUESTNVRAMPATH=/var/lib/libvirt/qemu/nvram/ diff --git a/packaging/systemd/cloudstack-management.default b/packaging/systemd/cloudstack-management.default index 994a1ee86997..a41338beda68 100644 --- a/packaging/systemd/cloudstack-management.default +++ b/packaging/systemd/cloudstack-management.default @@ -17,7 +17,7 @@ JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers -Djava.awt.headless=true -Xmx2G -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err --add-opens=java.base/java.lang=ALL-UNNAMED --add-exports=java.base/sun.security.x509=ALL-UNNAMED" -CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/java/mysql-connector-java.jar:/usr/share/cloudstack-mysql-ha/lib/*" +CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/cloudstack-mysql-ha/lib/*" BOOTSTRAP_CLASS=org.apache.cloudstack.ServerDaemon diff --git a/packaging/systemd/cloudstack-usage.default b/packaging/systemd/cloudstack-usage.default index 493f40c277a2..36b71ac3e0d6 100644 --- a/packaging/systemd/cloudstack-usage.default +++ b/packaging/systemd/cloudstack-usage.default @@ -17,7 +17,7 @@ JAVA_OPTS="-Xms256m -Xmx2048m --add-opens=java.base/java.lang=ALL-UNNAMED" -CLASSPATH="/usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage:/usr/share/java/mysql-connector-java.jar" +CLASSPATH="/usr/share/cloudstack-usage/*:/usr/share/cloudstack-usage/lib/*:/usr/share/cloudstack-mysql-ha/lib/*:/etc/cloudstack/usage" JAVA_CLASS=com.cloud.usage.UsageServer 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..a7121f149e30 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 @@ -226,12 +226,12 @@ public Pair takeBackup(final VirtualMachine vm, Boolean quiesce } catch (AgentUnavailableException e) { logger.error("Unable to contact backend control plane to initiate backup for VM {}", vm.getInstanceName()); backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); + backupDao.update(backupVO.getId(), backupVO); throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); } catch (OperationTimedoutException e) { logger.error("Operation to initiate backup timed out for VM {}", vm.getInstanceName()); backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); + backupDao.update(backupVO.getId(), backupVO); throw new CloudRuntimeException("Operation to initiate backup timed out, please try again"); } @@ -249,12 +249,12 @@ public Pair takeBackup(final VirtualMachine vm, Boolean quiesce } else { logger.error("Failed to take backup for VM {}: {}", vm.getInstanceName(), answer != null ? answer.getDetails() : "No answer received"); if (answer.getNeedsCleanup()) { - logger.error("Backup cleanup failed for VM {}. Leaving the backup in Error state.", vm.getInstanceName()); + logger.error("Backup cleanup failed for VM {}. Leaving the backup in Error state. Backup should be manually deleted to free up the space", vm.getInstanceName()); backupVO.setStatus(Backup.Status.Error); backupDao.update(backupVO.getId(), backupVO); } else { backupVO.setStatus(Backup.Status.Failed); - backupDao.remove(backupVO.getId()); + backupDao.update(backupVO.getId(), backupVO); } return new Pair<>(false, null); } diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java index 5ff036fef12f..d018d488c64a 100644 --- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java +++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManager.java @@ -40,17 +40,17 @@ public final class RootCACustomTrustManager implements X509TrustManager { private boolean authStrictness = true; private boolean allowExpiredCertificate = true; private CrlDao crlDao; - private X509Certificate caCertificate; + private List caCertificates; private Map activeCertMap; - public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map activeCertMap, final X509Certificate caCertificate, final CrlDao crlDao) { + public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map activeCertMap, final List caCertificates, final CrlDao crlDao) { if (StringUtils.isNotEmpty(clientAddress)) { this.clientAddress = clientAddress.replace("/", "").split(":")[0]; } this.authStrictness = authStrictness; this.allowExpiredCertificate = allowExpiredCertificate; this.activeCertMap = activeCertMap; - this.caCertificate = caCertificate; + this.caCertificates = caCertificates; this.crlDao = crlDao; } @@ -151,6 +151,6 @@ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) thr @Override public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[]{caCertificate}; + return caCertificates.toArray(new X509Certificate[0]); } } 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..afb4f561160e 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 @@ -40,7 +40,6 @@ import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.Enumeration; import java.util.HashSet; import java.util.List; @@ -60,6 +59,7 @@ import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.config.ValidatedConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.utils.security.CertUtils; import org.apache.cloudstack.utils.security.KeyStoreUtils; @@ -92,6 +92,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con private static KeyPair caKeyPair = null; private static X509Certificate caCertificate = null; + private static List caCertificates = null; private static KeyStore managementKeyStore = null; @Inject @@ -103,20 +104,25 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con /////////////// Root CA Settings /////////////////// //////////////////////////////////////////////////// - private static ConfigKey rootCAPrivateKey = new ConfigKey<>("Hidden", String.class, - "ca.plugin.root.private.key", - null, - "The ROOT CA private key.", true); - - private static ConfigKey rootCAPublicKey = new ConfigKey<>("Hidden", String.class, - "ca.plugin.root.public.key", - null, - "The ROOT CA public key.", true); - - private static ConfigKey rootCACertificate = new ConfigKey<>("Hidden", String.class, - "ca.plugin.root.ca.certificate", - null, - "The ROOT CA certificate.", true); + private static ConfigKey rootCAPrivateKey = new ValidatedConfigKey<>("Hidden", String.class, + "ca.plugin.root.private.key", null, + "The ROOT CA private key in PEM format. " + + "When set along with the public key and certificate, CloudStack uses this custom CA instead of auto-generating one. " + + "All three ca.plugin.root.* keys must be set together. Restart management server(s) when changed.", + false, ConfigKey.Scope.Global, null, RootCAProvider::validatePrivateKeyPem); + + private static ConfigKey rootCAPublicKey = new ValidatedConfigKey<>("Hidden", String.class, + "ca.plugin.root.public.key", null, + "The ROOT CA public key in PEM format (X.509/SPKI: must start with '-----BEGIN PUBLIC KEY-----'). " + + "Required when providing a custom CA. Restart management server(s) when changed.", + false, ConfigKey.Scope.Global, null, RootCAProvider::validatePublicKeyPem); + + private static ConfigKey rootCACertificate = new ValidatedConfigKey<>("Hidden", String.class, + "ca.plugin.root.ca.certificate", null, + "The CA certificate(s) in PEM format (must start with '-----BEGIN CERTIFICATE-----'). " + + "For intermediate CAs, concatenate the signing cert first, followed by intermediate(s) and root. " + + "Required when providing a custom CA. Restart management server(s) when changed.", + false, ConfigKey.Scope.Global, null, RootCAProvider::validateCACertificatePem); private static ConfigKey rootCAIssuerDN = new ConfigKey<>("Advanced", String.class, "ca.plugin.root.issuer.dn", @@ -151,7 +157,7 @@ private Certificate generateCertificate(final List domainNames, final Li caCertificate, caKeyPair, keyPair.getPublic(), subject, CAManager.CertSignatureAlgorithm.value(), validityDays, domainNames, ipAddresses); - return new Certificate(clientCertificate, keyPair.getPrivate(), Collections.singletonList(caCertificate)); + return new Certificate(clientCertificate, keyPair.getPrivate(), caCertificates); } private Certificate generateCertificateUsingCsr(final String csr, final List names, final List ips, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException { @@ -205,7 +211,7 @@ private Certificate generateCertificateUsingCsr(final String csr, final List getCaCertificate() { - return Collections.singletonList(caCertificate); + return caCertificates; } @Override @@ -254,8 +260,8 @@ public boolean revokeCertificate(final BigInteger certSerial, final String certC private KeyStore getCaKeyStore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException { final KeyStore ks = KeyStore.getInstance("JKS"); ks.load(null, null); - if (caKeyPair != null && caCertificate != null) { - ks.setKeyEntry(caAlias, caKeyPair.getPrivate(), getKeyStorePassphrase(), new X509Certificate[]{caCertificate}); + if (caKeyPair != null && CollectionUtils.isNotEmpty(caCertificates)) { + ks.setKeyEntry(caAlias, caKeyPair.getPrivate(), getKeyStorePassphrase(), caCertificates.toArray(new X509Certificate[0])); } else { return null; } @@ -274,7 +280,7 @@ public SSLEngine createSSLEngine(final SSLContext sslContext, final String remot final boolean authStrictness = rootCAAuthStrictness.value(); final boolean allowExpiredCertificate = rootCAAllowExpiredCert.value(); - TrustManager[] tms = new TrustManager[]{new RootCACustomTrustManager(remoteAddress, authStrictness, allowExpiredCertificate, certMap, caCertificate, crlDao)}; + TrustManager[] tms = new TrustManager[]{new RootCACustomTrustManager(remoteAddress, authStrictness, allowExpiredCertificate, certMap, caCertificates, crlDao)}; sslContext.init(kmf.getKeyManagers(), tms, new SecureRandom()); final SSLEngine sslEngine = sslContext.createSSLEngine(); @@ -316,33 +322,39 @@ private boolean saveNewRootCAKeypair() { if (!configDao.update(rootCAPrivateKey.key(), rootCAPrivateKey.category(), CertUtils.privateKeyToPem(keyPair.getPrivate()))) { logger.error("Failed to save RootCA private key"); } + caKeyPair = keyPair; } catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) { logger.error("Failed to generate/save RootCA private/public keys due to exception:", e); } - return loadRootCAKeyPair(); + return caKeyPair != null && caKeyPair.getPrivate() != null && caKeyPair.getPublic() != null; } - private boolean saveNewRootCACertificate() { + boolean saveNewRootCACertificate() { if (caKeyPair == null) { throw new CloudRuntimeException("Cannot issue self-signed root CA certificate as CA keypair is not initialized"); } try { logger.debug("Generating root CA certificate"); - final X509Certificate rootCaCertificate = CertUtils.generateV3Certificate( + final X509Certificate generatedCACert = CertUtils.generateV3Certificate( null, caKeyPair, caKeyPair.getPublic(), rootCAIssuerDN.value(), CAManager.CertSignatureAlgorithm.value(), getCaValidityDays(), null, null); - if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(rootCaCertificate))) { + if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(generatedCACert))) { logger.error("Failed to update RootCA public/x509 certificate"); } + caCertificates = new ArrayList<>(java.util.Collections.singletonList(generatedCACert)); + caCertificate = generatedCACert; } catch (final CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException | InvalidKeyException | OperatorCreationException | IOException e) { logger.error("Failed to generate RootCA certificate from private/public keys due to exception:", e); return false; } - return loadRootCACertificate(); + return caCertificate != null; } private boolean loadRootCAKeyPair() { + if (caKeyPair != null) { + return true; + } if (StringUtils.isAnyEmpty(rootCAPublicKey.value(), rootCAPrivateKey.value())) { return false; } @@ -355,14 +367,35 @@ private boolean loadRootCAKeyPair() { return caKeyPair.getPrivate() != null && caKeyPair.getPublic() != null; } - private boolean loadRootCACertificate() { + boolean loadRootCACertificate() { + if (caCertificate != null && CollectionUtils.isNotEmpty(caCertificates)) { + return true; + } + caCertificate = null; + caCertificates = null; if (StringUtils.isEmpty(rootCACertificate.value())) { return false; } try { - caCertificate = CertUtils.pemToX509Certificate(rootCACertificate.value()); - caCertificate.verify(caKeyPair.getPublic()); - } catch (final IOException | CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) { + final List loadedCerts = CertUtils.pemToX509Certificates(rootCACertificate.value()); + if (CollectionUtils.isEmpty(loadedCerts)) { + logger.error("No certificates found in ca.plugin.root.ca.certificate"); + return false; + } + final X509Certificate loadedCACert = loadedCerts.get(0); + + // Verify key ownership without enforcing self-signature + if (!loadedCACert.getPublicKey().equals(caKeyPair.getPublic())) { + logger.error("The public key in the CA certificate does not match the configured CA public key"); + return false; + } + + if (loadedCerts.size() > 1) { + logger.info("Loaded CA certificate chain with {} certificate(s)", loadedCerts.size()); + } + caCertificates = loadedCerts; + caCertificate = loadedCACert; + } catch (final IOException | CertificateException e) { logger.error("Failed to load saved RootCA certificate due to exception:", e); return false; } @@ -389,9 +422,15 @@ private boolean loadManagementKeyStore() { try { managementKeyStore = KeyStore.getInstance("JKS"); managementKeyStore.load(null, null); - managementKeyStore.setCertificateEntry(caAlias, caCertificate); + int caIndex = 0; + for (final X509Certificate cert : caCertificates) { + managementKeyStore.setCertificateEntry(caAlias + "-" + caIndex++, cert); + } + final List fullChain = new ArrayList<>(); + fullChain.add(serverCertificate.getClientCertificate()); + fullChain.addAll(caCertificates); managementKeyStore.setKeyEntry(managementAlias, serverCertificate.getPrivateKey(), getKeyStorePassphrase(), - new X509Certificate[]{serverCertificate.getClientCertificate(), caCertificate}); + fullChain.toArray(new X509Certificate[0])); } catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException e) { logger.error("Failed to load root CA management-server keystore due to exception: ", e); return false; @@ -421,14 +460,63 @@ protected void addConfiguredManagementIp(List ipList) { } + private static void validatePrivateKeyPem(String value) { + if (StringUtils.isEmpty(value)) return; + try { + CertUtils.pemToPrivateKey(value); + } catch (InvalidKeySpecException | IOException e) { + throw new IllegalArgumentException( + "ca.plugin.root.private.key is not a valid PEM private key: " + e.getMessage()); + } + } + + private static void validatePublicKeyPem(String value) { + if (StringUtils.isEmpty(value)) return; + try { + CertUtils.pemToPublicKey(value); + } catch (InvalidKeySpecException | IOException e) { + throw new IllegalArgumentException( + "ca.plugin.root.public.key is not a valid PEM public key: " + e.getMessage()); + } + } + + static void validateCACertificatePem(String value) { + if (StringUtils.isEmpty(value)) return; + try { + final List certs = CertUtils.pemToX509Certificates(value); + if (CollectionUtils.isEmpty(certs)) { + throw new IllegalArgumentException( + "ca.plugin.root.ca.certificate contains no certificates"); + } + } catch (IOException | CertificateException e) { + throw new IllegalArgumentException( + "ca.plugin.root.ca.certificate is not a valid PEM certificate: " + e.getMessage()); + } + } + private boolean setupCA() { - if (!loadRootCAKeyPair() && !saveNewRootCAKeypair()) { - logger.error("Failed to save and load root CA keypair"); - return false; + if (!loadRootCAKeyPair()) { + if (hasUserProvidedCAKeys()) { + logger.error("Failed to load user-provided CA keys from configuration. " + + "Check that ca.plugin.root.private.key, ca.plugin.root.public.key, and " + + "ca.plugin.root.ca.certificate are all set and in the correct PEM format. " + + "Overwriting with auto-generated keys."); + } + if (!saveNewRootCAKeypair()) { + logger.error("Failed to save and load root CA keypair"); + return false; + } } - if (!loadRootCACertificate() && !saveNewRootCACertificate()) { - logger.error("Failed to save and load root CA certificate"); - return false; + if (!loadRootCACertificate()) { + if (hasUserProvidedCAKeys()) { + logger.error("Failed to load user-provided CA certificate. " + + "Check that ca.plugin.root.ca.certificate is set and in PEM format. " + + "Overwriting with auto-generated certificate."); + } + if (!saveNewRootCACertificate()) { + logger.error("Failed to save and load root CA certificate"); + return false; + } } if (!loadManagementKeyStore()) { logger.error("Failed to check and configure management server keystore"); @@ -437,10 +525,16 @@ private boolean setupCA() { return true; } + private boolean hasUserProvidedCAKeys() { + return StringUtils.isNotEmpty(rootCAPublicKey.value()) + || StringUtils.isNotEmpty(rootCAPrivateKey.value()) + || StringUtils.isNotEmpty(rootCACertificate.value()); + } + @Override public boolean start() { managementCertificateCustomSAN = CAManager.CertManagementCustomSubjectAlternativeName.value(); - return loadRootCAKeyPair() && loadRootCAKeyPair() && loadManagementKeyStore(); + return loadRootCAKeyPair() && loadRootCACertificate() && loadManagementKeyStore(); } @Override diff --git a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java index d4ded3023321..714e18c3449f 100644 --- a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java +++ b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCACustomTrustManagerTest.java @@ -23,9 +23,11 @@ import java.security.KeyPair; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.List; import org.apache.cloudstack.utils.security.CertUtils; import org.junit.Assert; @@ -63,14 +65,14 @@ public void setUp() throws Exception { @Test public void testAuthNotStrictWithInvalidCert() throws Exception { - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(null, null); } @Test public void testAuthNotStrictWithRevokedCert() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(new CrlVO()); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA"); Assert.assertTrue(certMap.containsKey(clientIp)); Assert.assertEquals(certMap.get(clientIp), caCertificate); @@ -79,7 +81,7 @@ public void testAuthNotStrictWithRevokedCert() throws Exception { @Test public void testAuthNotStrictWithInvalidCertOwnership() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA"); Assert.assertTrue(certMap.containsKey(clientIp)); Assert.assertEquals(certMap.get(clientIp), caCertificate); @@ -88,14 +90,14 @@ public void testAuthNotStrictWithInvalidCertOwnership() throws Exception { @Test(expected = CertificateException.class) public void testAuthNotStrictWithDenyExpiredCertAndOwnership() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, false, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, false, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA"); } @Test public void testAuthNotStrictWithAllowExpiredCertAndOwnership() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA"); Assert.assertTrue(certMap.containsKey(clientIp)); Assert.assertEquals(certMap.get(clientIp), expiredClientCertificate); @@ -103,35 +105,50 @@ public void testAuthNotStrictWithAllowExpiredCertAndOwnership() throws Exception @Test(expected = CertificateException.class) public void testAuthStrictWithInvalidCert() throws Exception { - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(null, null); } @Test(expected = CertificateException.class) public void testAuthStrictWithRevokedCert() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(new CrlVO()); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA"); } @Test(expected = CertificateException.class) public void testAuthStrictWithInvalidCertOwnership() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA"); } @Test(expected = CertificateException.class) public void testAuthStrictWithDenyExpiredCertAndOwnership() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, Collections.singletonList(caCertificate), crlDao); trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA"); } + @Test + public void testGetAcceptedIssuersWithChain() throws Exception { + final KeyPair rootKeyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate rootCert = CertUtils.generateV3Certificate(null, rootKeyPair, rootKeyPair.getPublic(), + "CN=root", "SHA256withRSA", 365, null, null); + final List chain = Arrays.asList(caCertificate, rootCert); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager( + clientIp, false, true, certMap, chain, crlDao); + + final X509Certificate[] issuers = trustManager.getAcceptedIssuers(); + Assert.assertEquals(2, issuers.length); + Assert.assertEquals(caCertificate, issuers[0]); + Assert.assertEquals(rootCert, issuers[1]); + } + @Test public void testAuthStrictWithAllowExpiredCertAndOwnership() throws Exception { Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null); - final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao); + final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, Collections.singletonList(caCertificate), crlDao); Assert.assertTrue(trustManager.getAcceptedIssuers() != null); Assert.assertTrue(trustManager.getAcceptedIssuers().length == 1); Assert.assertEquals(trustManager.getAcceptedIssuers()[0], caCertificate); diff --git a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java index 8311f4d45abc..21f00c66a1df 100644 --- a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java +++ b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java @@ -31,6 +31,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -38,6 +39,7 @@ import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.utils.security.CertUtils; import org.apache.cloudstack.utils.security.SSLUtils; import org.bouncycastle.asn1.x509.GeneralName; @@ -49,7 +51,6 @@ import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.test.util.ReflectionTestUtils; @RunWith(MockitoJUnitRunner.class) @@ -75,7 +76,7 @@ public void setUp() throws Exception { addField(provider, "caKeyPair", caKeyPair); addField(provider, "caCertificate", caCertificate); - addField(provider, "caKeyPair", caKeyPair); + addField(provider, "caCertificates", Collections.singletonList(caCertificate)); } @After @@ -129,6 +130,46 @@ public void testIssueCertificateWithCsr() throws NoSuchProviderException, Certif certificate.getClientCertificate().verify(caCertificate.getPublicKey()); } + @Test + public void testGetCaCertificateWithChain() throws Exception { + final KeyPair rootKeyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate rootCert = CertUtils.generateV3Certificate(null, rootKeyPair, rootKeyPair.getPublic(), + "CN=root", "SHA256withRSA", 365, null, null); + final KeyPair intermediateKeyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate intermediateCert = CertUtils.generateV3Certificate(rootCert, rootKeyPair, + intermediateKeyPair.getPublic(), "CN=intermediate", "SHA256withRSA", 365, null, null); + + final List chain = Arrays.asList(intermediateCert, rootCert); + addField(provider, "caKeyPair", intermediateKeyPair); + addField(provider, "caCertificate", intermediateCert); + addField(provider, "caCertificates", chain); + + Assert.assertEquals(2, provider.getCaCertificate().size()); + Assert.assertEquals(intermediateCert, provider.getCaCertificate().get(0)); + Assert.assertEquals(rootCert, provider.getCaCertificate().get(1)); + } + + @Test + public void testIssueCertificateWithoutCsrAndChain() throws Exception { + final KeyPair rootKeyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate rootCert = CertUtils.generateV3Certificate(null, rootKeyPair, rootKeyPair.getPublic(), + "CN=root", "SHA256withRSA", 365, null, null); + final KeyPair intermediateKeyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate intermediateCert = CertUtils.generateV3Certificate(rootCert, rootKeyPair, + intermediateKeyPair.getPublic(), "CN=intermediate", "SHA256withRSA", 365, null, null); + + addField(provider, "caKeyPair", intermediateKeyPair); + addField(provider, "caCertificate", intermediateCert); + addField(provider, "caCertificates", Arrays.asList(intermediateCert, rootCert)); + + final Certificate certificate = provider.issueCertificate(Arrays.asList("domain1.com"), null, 1); + Assert.assertNotNull(certificate); + Assert.assertEquals(2, certificate.getCaCertificates().size()); + Assert.assertEquals(intermediateCert, certificate.getCaCertificates().get(0)); + Assert.assertEquals(rootCert, certificate.getCaCertificates().get(1)); + certificate.getClientCertificate().verify(intermediateKeyPair.getPublic()); + } + @Test public void testRevokeCertificate() throws Exception { Assert.assertTrue(provider.revokeCertificate(CertUtils.generateRandomBigInt(), "anyString")); @@ -177,8 +218,8 @@ public void testIsManagementCertificateNoAltNames() { } @Test - public void testIsManagementCertificateNoMatch() { - ReflectionTestUtils.setField(provider, "managementCertificateCustomSAN", "cloudstack"); + public void testIsManagementCertificateNoMatch() throws Exception { + addField(provider, "managementCertificateCustomSAN", "cloudstack"); try { X509Certificate certificate = Mockito.mock(X509Certificate.class); List> altNames = new ArrayList<>(); @@ -193,9 +234,9 @@ public void testIsManagementCertificateNoMatch() { } @Test - public void testIsManagementCertificateMatch() { + public void testIsManagementCertificateMatch() throws Exception { String customSAN = "cloudstack"; - ReflectionTestUtils.setField(provider, "managementCertificateCustomSAN", customSAN); + addField(provider, "managementCertificateCustomSAN", customSAN); try { X509Certificate certificate = Mockito.mock(X509Certificate.class); List> altNames = new ArrayList<>(); @@ -208,4 +249,58 @@ public void testIsManagementCertificateMatch() { Assert.fail(String.format("Exception occurred: %s", e.getMessage())); } } + + @Test + public void testLoadRootCACertificateWithMismatchedCert() throws Exception { + KeyPair otherKeyPair = CertUtils.generateRandomKeyPair(1024); + X509Certificate mismatchedCert = CertUtils.generateV3Certificate(null, otherKeyPair, otherKeyPair.getPublic(), "CN=other", "SHA256withRSA", 365, null, null); + String mismatchedPem = CertUtils.x509CertificateToPem(mismatchedCert); + + ConfigKey mockCertKey = Mockito.mock(ConfigKey.class); + Mockito.when(mockCertKey.value()).thenReturn(mismatchedPem); + addField(provider, "rootCACertificate", mockCertKey); + + addField(provider, "caCertificate", null); + addField(provider, "caCertificates", null); + + Boolean result = provider.loadRootCACertificate(); + Assert.assertFalse(result); + Assert.assertNull(provider.getCaCertificate()); + } + + @Test + public void testSaveNewRootCACertificateWithStaleCache() throws Exception { + ConfigurationDao configDao = Mockito.mock(ConfigurationDao.class); + addField(provider, "configDao", configDao); + + ConfigKey mockCertKey = Mockito.mock(ConfigKey.class); + Mockito.when(mockCertKey.key()).thenReturn("ca.plugin.root.ca.certificate"); + Mockito.when(mockCertKey.category()).thenReturn("Hidden"); + addField(provider, "rootCACertificate", mockCertKey); + + ConfigKey mockIssuerKey = Mockito.mock(ConfigKey.class); + Mockito.when(mockIssuerKey.value()).thenReturn("CN=ca.cloudstack.apache.org"); + addField(provider, "rootCAIssuerDN", mockIssuerKey); + + addField(provider, "caCertificate", null); + addField(provider, "caCertificates", null); + + Mockito.when(configDao.update(Mockito.anyString(), Mockito.anyString(), Mockito.anyString())).thenReturn(true); + + Boolean result = provider.saveNewRootCACertificate(); + Assert.assertTrue(result); + Assert.assertNotNull(provider.getCaCertificate()); + Assert.assertEquals(1, provider.getCaCertificate().size()); + } + + @Test + public void testValidateCACertificatePem() throws Exception { + String truncatedPem = "-----BEGIN CERTIFICATE-----\nMIICxTCCAa0CAQAw\n"; + try { + RootCAProvider.validateCACertificatePem(truncatedPem); + Assert.fail("Expected IllegalArgumentException"); + } catch (IllegalArgumentException e) { + Assert.assertTrue(e.getMessage().contains("is not a valid PEM certificate")); + } + } } 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..e5c89c5de1c5 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 @@ -17,12 +17,9 @@ package org.apache.cloudstack.api.command; import java.util.Date; -import java.util.List; import javax.inject.Inject; -import com.cloud.user.Account; - import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -30,36 +27,41 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.QuotaBalanceResponse; import org.apache.cloudstack.api.response.QuotaResponseBuilder; -import org.apache.cloudstack.quota.vo.QuotaBalanceVO; -import org.apache.cloudstack.api.response.QuotaStatementItemResponse; +import org.apache.commons.lang3.ObjectUtils; -@APICommand(name = "quotaBalance", responseObject = QuotaStatementItemResponse.class, description = "Create a quota balance statement", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, +@APICommand(name = "quotaBalance", responseObject = QuotaBalanceResponse.class, description = "Create Quota balance statements for the provided Accounts or Projects.", since = "4.7.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, httpMethod = "GET") public class QuotaBalanceCmd extends BaseCmd { - - - @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, required = true, description = "Account Id for which statement needs to be generated") + @ACL + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "Name of the Account for which balance statements will be generated. " + + "Deprecated, please use 'accountid' instead.") private String accountName; @ACL - @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, required = true, entityType = DomainResponse.class, description = "If domain Id is given and the caller is domain admin then the statement is generated for domain.") + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "ID of the domain which the Account identified by 'account' belongs to." + + "Deprecated, please use 'accountid' instead.") private Long domainId; - @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "End of the period of the Quota balance." + - ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Date of the last Quota balance to be returned. Must be informed together with the " + + "startdate parameter, and can not be before startdate. " + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) private Date endDate; - @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start of the period of the Quota balance. " + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Date of the first Quota balance to be returned. Must be before today. " + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS) private Date startDate; @ACL - @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "List usage records for the specified Account") + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "ID of the Account for which balance statements will be generated.") private Long accountId; + @ACL + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "ID of the Project for which balance statements will be generated. Can not be specified with accountid.") + private Long projectId; + @Inject QuotaResponseBuilder _responseBuilder; @@ -88,48 +90,32 @@ public void setDomainId(Long domainId) { } public Date getEndDate() { - if (endDate == null){ - return null; - } - else{ - return _responseBuilder.startOfNextDay(new Date(endDate.getTime())); - } + return endDate; } public void setEndDate(Date endDate) { - this.endDate = endDate == null ? null : new Date(endDate.getTime()); + this.endDate = endDate; } public Date getStartDate() { - return startDate == null ? null : new Date(startDate.getTime()); + return startDate; } public void setStartDate(Date startDate) { - this.startDate = startDate == null ? null : new Date(startDate.getTime()); + this.startDate = startDate; } @Override public long getEntityOwnerId() { - if (accountId != null) { - return accountId; - } - Account account = _accountService.getActiveAccountByName(accountName, domainId); - if (account != null) { - return account.getAccountId(); + if (ObjectUtils.allNull(accountId, accountName, domainId, projectId)) { + return -1; } - return Account.ACCOUNT_ID_SYSTEM; + return _accountService.finalizeAccountId(accountId, accountName, domainId, projectId); } @Override public void execute() { - List quotaUsage = _responseBuilder.getQuotaBalance(this); - - QuotaBalanceResponse response; - if (endDate == null) { - response = _responseBuilder.createQuotaLastBalanceResponse(quotaUsage, getStartDate()); - } else { - response = _responseBuilder.createQuotaBalanceResponse(quotaUsage, getStartDate(), new Date(endDate.getTime())); - } + QuotaBalanceResponse response = _responseBuilder.createQuotaBalanceResponse(this); response.setResponseName(getCommandName()); setResponseObject(response); } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java index 625f4829c673..714066b048ad 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaBalanceResponse.java @@ -17,137 +17,65 @@ package org.apache.cloudstack.api.response; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.ArrayList; import java.util.Date; import java.util.List; import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; -import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import com.cloud.serializer.Param; public class QuotaBalanceResponse extends BaseResponse { - @SerializedName("accountid") - @Param(description = "Account ID") - private Long accountId; - - @SerializedName("account") - @Param(description = "Account name") - private String accountName; - - @SerializedName("domain") - @Param(description = "Domain ID") - private Long domainId; - - @SerializedName("startquota") - @Param(description = "Quota started with") - private BigDecimal startQuota; - - @SerializedName("endquota") - @Param(description = "Quota by end of this period") - private BigDecimal endQuota; - - @SerializedName("credits") - @Param(description = "List of credits made during this period") - private List credits = null; - - @SerializedName("startdate") - @Param(description = "Start date") - private Date startDate = null; - - @SerializedName("enddate") - @Param(description = "End date") - private Date endDate = null; - - @SerializedName("currency") - @Param(description = "Currency") + @SerializedName(ApiConstants.CURRENCY) + @Param(description = "Balance's currency.") private String currency; - public QuotaBalanceResponse() { - super(); - credits = new ArrayList(); - } - - public Long getAccountId() { - return accountId; - } - - public void setAccountId(Long accountId) { - this.accountId = accountId; - } - - public String getAccountName() { - return accountName; - } + @SerializedName(ApiConstants.DATE) + @Param(description = "Balance's date.") + private Date date; - public void setAccountName(String accountName) { - this.accountName = accountName; - } + @SerializedName(ApiConstants.BALANCE) + @Param(description = "Balance's value.") + private BigDecimal balance; - public Long getDomainId() { - return domainId; - } - - public void setDomainId(Long domainId) { - this.domainId = domainId; - } + @SerializedName(ApiConstants.BALANCES) + @Param(description = "List of balances in the period.") + private List balances; - public BigDecimal getStartQuota() { - return startQuota; - } - - public void setStartQuota(BigDecimal startQuota) { - this.startQuota = startQuota.setScale(2, RoundingMode.HALF_EVEN); - } - - public BigDecimal getEndQuota() { - return endQuota; - } - - public void setEndQuota(BigDecimal endQuota) { - this.endQuota = endQuota.setScale(2, RoundingMode.HALF_EVEN); - } - - public List getCredits() { - return credits; - } - - public void setCredits(List credits) { - this.credits = credits; + public QuotaBalanceResponse() { + super("balance"); } - public void addCredits(QuotaBalanceVO credit) { - QuotaCreditsResponse cr = new QuotaCreditsResponse(); - cr.setCredit(credit.getCreditBalance()); - cr.setCreditedOn(credit.getUpdatedOn()); - credits.add(0, cr); + public QuotaBalanceResponse(Date date, BigDecimal balance) { + super("balance"); + this.date = date; + this.balance = balance; } - public Date getStartDate() { - return startDate == null ? null : new Date(startDate.getTime()); + public void setCurrency(String currency) { + this.currency = currency; } - public void setStartDate(Date startDate) { - this.startDate = startDate == null ? null : new Date(startDate.getTime()); + public void setBalances(List balances) { + this.balances = balances; } - public Date getEndDate() { - return endDate == null ? null : new Date(endDate.getTime()); + public String getCurrency() { + return currency; } - public void setEndDate(Date endDate) { - this.endDate = endDate == null ? null : new Date(endDate.getTime()); + public List getBalances() { + return balances; } - public String getCurrency() { - return currency; + public Date getDate() { + return date; } - public void setCurrency(String currency) { - this.currency = currency; + public BigDecimal getBalance() { + return balance; } } diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java index bde905c487b7..f0390bf626d0 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/api/response/QuotaResponseBuilder.java @@ -29,7 +29,6 @@ import org.apache.cloudstack.api.command.QuotaTariffListCmd; import org.apache.cloudstack.api.command.QuotaTariffUpdateCmd; import org.apache.cloudstack.api.command.QuotaValidateActivationRuleCmd; -import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaEmailConfigurationVO; import org.apache.cloudstack.quota.vo.QuotaTariffVO; @@ -50,14 +49,10 @@ public interface QuotaResponseBuilder { QuotaStatementResponse createQuotaStatementResponse(QuotaStatementCmd cmd); - QuotaBalanceResponse createQuotaBalanceResponse(List quotaUsage, Date startDate, Date endDate); + QuotaBalanceResponse createQuotaBalanceResponse(QuotaBalanceCmd cmd); Pair, Integer> createQuotaSummaryResponse(QuotaSummaryCmd cmd); - QuotaBalanceResponse createQuotaLastBalanceResponse(List quotaBalance, Date startDate); - - List getQuotaBalance(QuotaBalanceCmd cmd); - QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Double amount, Long updatedBy, Boolean enforce); List listQuotaEmailTemplates(QuotaEmailTemplateListCmd cmd); 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 173c0723731b..231ba777e600 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 @@ -26,13 +26,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; -import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; -import java.util.Iterator; import java.util.List; -import java.util.ListIterator; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -98,6 +95,7 @@ import org.apache.cloudstack.quota.activationrule.presetvariables.GenericPresetVariable; import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariableDefinition; import org.apache.cloudstack.quota.activationrule.presetvariables.PresetVariables; +import org.apache.cloudstack.quota.activationrule.presetvariables.ResourceCounting; import org.apache.cloudstack.quota.activationrule.presetvariables.Value; import org.apache.cloudstack.quota.constant.QuotaConfig; import org.apache.cloudstack.quota.constant.QuotaTypes; @@ -185,7 +183,7 @@ public class QuotaResponseBuilderImpl implements QuotaResponseBuilder { private VolumeDao volumeDao; - private final Class[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class}; + private final Class[] assignableClasses = {GenericPresetVariable.class, ComputingResources.class, ResourceCounting.class}; private Set accountTypesThatCanListAllQuotaSummaries = Sets.newHashSet(Account.Type.ADMIN, Account.Type.DOMAIN_ADMIN); @@ -328,93 +326,18 @@ public boolean isUserAllowedToSeeActivationRules(User user) { } @Override - public QuotaBalanceResponse createQuotaBalanceResponse(List quotaBalance, Date startDate, Date endDate) { - if (quotaBalance == null || quotaBalance.isEmpty()) { - throw new InvalidParameterValueException("The request period does not contain balance entries."); - } - Collections.sort(quotaBalance, new Comparator() { - @Override - public int compare(QuotaBalanceVO o1, QuotaBalanceVO o2) { - o1 = o1 == null ? new QuotaBalanceVO() : o1; - o2 = o2 == null ? new QuotaBalanceVO() : o2; - return o2.getUpdatedOn().compareTo(o1.getUpdatedOn()); // desc - } - }); - - boolean have_balance_entries = false; - //check that there is at least one balance entry - for (Iterator it = quotaBalance.iterator(); it.hasNext();) { - QuotaBalanceVO entry = it.next(); - if (entry.isBalanceEntry()) { - have_balance_entries = true; - break; - } - } - //if last entry is a credit deposit then remove that as that is already - //accounted for in the starting balance after that entry, note the sort is desc - if (have_balance_entries) { - ListIterator li = quotaBalance.listIterator(quotaBalance.size()); - // Iterate in reverse. - while (li.hasPrevious()) { - QuotaBalanceVO entry = li.previous(); - if (logger.isDebugEnabled()) { - logger.debug("createQuotaBalanceResponse: Entry=" + entry); - } - if (entry.getCreditsId() > 0) { - li.remove(); - } else { - break; - } - } - } + public QuotaBalanceResponse createQuotaBalanceResponse(QuotaBalanceCmd cmd) { + List quotaBalances = _quotaService.listQuotaBalancesForAccount(cmd.getEntityOwnerId(), cmd.getStartDate(), cmd.getEndDate()); - int quota_activity = quotaBalance.size(); - QuotaBalanceResponse resp = new QuotaBalanceResponse(); - BigDecimal lastCredits = new BigDecimal(0); - boolean consecutive = true; - for (Iterator it = quotaBalance.iterator(); it.hasNext();) { - QuotaBalanceVO entry = it.next(); - if (logger.isDebugEnabled()) { - logger.debug("createQuotaBalanceResponse: All Credit Entry=" + entry); - } - if (entry.getCreditsId() > 0) { - if (consecutive) { - lastCredits = lastCredits.add(entry.getCreditBalance()); - } - resp.addCredits(entry); - it.remove(); - } else { - consecutive = false; - } - } + List balances = quotaBalances.stream() + .map(balance -> new QuotaBalanceResponse(balance.getUpdatedOn(), balance.getCreditBalance())) + .collect(Collectors.toList()); - if (quota_activity > 0 && quotaBalance.size() > 0) { - // order is desc last item is the start item - QuotaBalanceVO startItem = quotaBalance.get(quotaBalance.size() - 1); - QuotaBalanceVO endItem = quotaBalance.get(0); - resp.setStartDate(startDate); - resp.setStartQuota(startItem.getCreditBalance()); - resp.setEndDate(endDate); - if (logger.isDebugEnabled()) { - logger.debug("createQuotaBalanceResponse: Start Entry=" + startItem); - logger.debug("createQuotaBalanceResponse: End Entry=" + endItem); - } - resp.setEndQuota(endItem.getCreditBalance().add(lastCredits)); - } else if (quota_activity > 0) { - // order is desc last item is the start item - resp.setStartDate(startDate); - resp.setStartQuota(new BigDecimal(0)); - resp.setEndDate(endDate); - resp.setEndQuota(new BigDecimal(0).add(lastCredits)); - } else { - resp.setStartDate(startDate); - resp.setEndDate(endDate); - resp.setStartQuota(new BigDecimal(0)); - resp.setEndQuota(new BigDecimal(0)); - } - resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); - resp.setObjectName("balance"); - return resp; + QuotaBalanceResponse response = new QuotaBalanceResponse(); + response.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); + response.setBalances(balances); + + return response; } @Override @@ -783,7 +706,7 @@ public QuotaCreditsResponse addQuotaCredits(Long accountId, Long domainId, Doubl throw new InvalidParameterValueException("Account does not exist with account id " + accountId); } final boolean lockAccountEnforcement = "true".equalsIgnoreCase(QuotaConfig.QuotaEnableEnforcement.value()); - final BigDecimal currentAccountBalance = _quotaBalanceDao.lastQuotaBalance(accountId, domainId, startOfNextDay(new Date(depositedOn.getTime()))); + final BigDecimal currentAccountBalance = _quotaBalanceDao.getLastQuotaBalance(accountId, domainId); logger.debug("Depositing [{}] credits on adjusted date [{}]; current balance is [{}].", amount, DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), depositedOn), currentAccountBalance); // update quota account with the balance @@ -847,34 +770,6 @@ public boolean updateQuotaEmailTemplate(QuotaEmailTemplateUpdateCmd cmd) { return false; } - @Override - public QuotaBalanceResponse createQuotaLastBalanceResponse(List quotaBalance, Date startDate) { - if (quotaBalance == null) { - throw new InvalidParameterValueException("There are no balance entries on or before the requested date."); - } - if (startDate == null) { - startDate = new Date(); - } - QuotaBalanceResponse resp = new QuotaBalanceResponse(); - BigDecimal lastCredits = new BigDecimal(0); - for (QuotaBalanceVO entry : quotaBalance) { - logger.debug("createQuotaLastBalanceResponse Date={} balance={} credit={}", - DateUtil.displayDateInTimezone(QuotaManagerImpl.getUsageAggregationTimeZone(), entry.getUpdatedOn()), - entry.getCreditBalance(), entry.getCreditsId()); - - lastCredits = lastCredits.add(entry.getCreditBalance()); - } - resp.setStartQuota(lastCredits); - resp.setStartDate(startDate); - resp.setCurrency(QuotaConfig.QuotaCurrencySymbol.value()); - resp.setObjectName("balance"); - return resp; - } - - @Override - public List getQuotaBalance(QuotaBalanceCmd cmd) { - return _quotaService.findQuotaBalanceVO(cmd.getAccountId(), cmd.getAccountName(), cmd.getDomainId(), cmd.getStartDate(), cmd.getEndDate()); - } @Override public Date startOfNextDay(Date date) { LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); diff --git a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java index 78acfc11682e..478e43d2e203 100644 --- a/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java +++ b/plugins/database/quota/src/main/java/org/apache/cloudstack/quota/QuotaService.java @@ -30,7 +30,7 @@ public interface QuotaService extends PluggableService { List getQuotaUsage(Long accountId, String accountName, Long domainId, Integer usageType, Date startDate, Date endDate); - List findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate); + List listQuotaBalancesForAccount(Long accountId, Date startDate, Date endDate); void setLockAccount(Long accountId, Boolean state); 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..e1ec2ebb2a60 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 @@ -28,6 +28,7 @@ import com.cloud.projects.ProjectManager; import com.cloud.user.AccountService; +import com.cloud.utils.DateUtil; import org.apache.cloudstack.api.command.QuotaBalanceCmd; import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaCreditsCmd; @@ -58,17 +59,16 @@ import org.apache.cloudstack.quota.vo.QuotaBalanceVO; import org.apache.cloudstack.quota.vo.QuotaUsageJoinVO; import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.time.DateUtils; import org.springframework.stereotype.Component; import com.cloud.configuration.Config; import com.cloud.domain.dao.DomainDao; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.PermissionDeniedException; import com.cloud.user.Account; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.utils.component.ManagerBase; -import com.cloud.utils.db.Filter; @Component public class QuotaServiceImpl extends ManagerBase implements QuotaService, Configurable, QuotaConfig { @@ -152,63 +152,53 @@ public Boolean isQuotaServiceEnabled() { } @Override - public List findQuotaBalanceVO(Long accountId, String accountName, Long domainId, Date startDate, Date endDate) { - if ((accountId == null) && (accountName != null) && (domainId != null)) { - Account userAccount = null; - Account caller = CallContext.current().getCallingAccount(); - if (_domainDao.isChildDomain(caller.getDomainId(), domainId)) { - Filter filter = new Filter(AccountVO.class, "id", Boolean.FALSE, null, null); - List accounts = _accountDao.listAccounts(accountName, domainId, filter); - if (!accounts.isEmpty()) { - userAccount = accounts.get(0); - } - if (userAccount != null) { - accountId = userAccount.getId(); - } else { - throw new InvalidParameterValueException("Unable to find account " + accountName + " in domain " + domainId); - } - } else { - throw new PermissionDeniedException("Invalid Domain Id or Account"); - } + public List listQuotaBalancesForAccount(Long accountId, Date startDate, Date endDate) { + validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, endDate); + + if (accountId == -1) { + accountId = CallContext.current().getCallingAccountId(); } + Account account = _accountDao.findByIdIncludingRemoved(accountId); + Long domainId = account.getDomainId(); - startDate = startDate == null ? new Date() : startDate; + if (startDate == null && endDate == null) { + logger.debug("Retrieving last quota balance for {}.", account); + QuotaBalanceVO lastQuotaBalance = _quotaBalanceDao.getLastQuotaBalanceEntry(accountId, domainId, null); - if (endDate == null) { - // adjust start date to end of day as there is no end date - startDate = _respBldr.startOfNextDay(startDate); - if (logger.isDebugEnabled()) { - logger.debug("getQuotaBalance1: Getting quota balance records for account: " + accountId + ", domainId: " + domainId + ", on or before " + startDate); - } - List qbrecords = _quotaBalanceDao.lastQuotaBalanceVO(accountId, domainId, startDate); - if (logger.isDebugEnabled()) { - logger.debug("Found records size=" + qbrecords.size()); - } - if (qbrecords.isEmpty()) { - logger.info("Incorrect Date there are no quota records before this date " + startDate); - return qbrecords; - } else { - return qbrecords; - } - } else { - if (startDate.before(endDate)) { - if (logger.isDebugEnabled()) { - logger.debug("getQuotaBalance2: Getting quota balance records for account: " + accountId + ", domainId: " + domainId + ", between " + startDate - + " and " + endDate); - } - List qbrecords = _quotaBalanceDao.findQuotaBalance(accountId, domainId, startDate, endDate); - if (logger.isDebugEnabled()) { - logger.debug("getQuotaBalance3: Found records size=" + qbrecords.size()); - } - if (qbrecords.isEmpty()) { - logger.info("There are no quota records between these dates start date " + startDate + " and end date:" + endDate); - return qbrecords; - } else { - return qbrecords; - } - } else { - throw new InvalidParameterValueException("Incorrect Date Range. Start date: " + startDate + " is after end date:" + endDate); + if (lastQuotaBalance == null) { + logger.debug("Did not found a quota balance entry for {}.", account); + return new ArrayList<>(); } + + return List.of(lastQuotaBalance); + } + + if (endDate == null) { + endDate = DateUtils.addDays(new Date(), -1); + } + + List quotaBalances = _quotaBalanceDao.listQuotaBalances(accountId, domainId, startDate, endDate); + + if (quotaBalances.isEmpty()) { + logger.info("There are no quota balances for {} between [{}] and [{}].", account, + DateUtil.getOutputString(startDate), DateUtil.getOutputString(endDate)); + } + + return quotaBalances; + } + + protected void validateStartDateAndEndDateForListQuotaBalancesForAccount(Date startDate, Date endDate) { + if (startDate == null && endDate != null) { + throw new InvalidParameterValueException("Parameter \"enddate\" must be informed together with parameter \"startdate\"."); + } + + Date now = new Date(); + if (startDate != null && startDate.after(now)) { + throw new InvalidParameterValueException("The last balance can be at most from yesterday; therefore, the start date must be before today."); + } + + if (ObjectUtils.allNotNull(startDate, endDate) && startDate.after(endDate)) { + throw new InvalidParameterValueException("The start date cannot be after the end date."); } } diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java index adabc694f25a..cd0eeb2c3b48 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/command/QuotaBalanceCmdTest.java @@ -16,18 +16,15 @@ // under the License. package org.apache.cloudstack.api.command; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - import org.apache.cloudstack.api.response.QuotaBalanceResponse; import org.apache.cloudstack.api.response.QuotaResponseBuilder; -import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import junit.framework.TestCase; @@ -36,31 +33,20 @@ public class QuotaBalanceCmdTest extends TestCase { @Mock - QuotaResponseBuilder responseBuilder; + QuotaResponseBuilder quotaResponseBuilderMock; - @Test - public void testQuotaBalanceCmd() throws NoSuchFieldException, IllegalAccessException { - QuotaBalanceCmd cmd = new QuotaBalanceCmd(); - Field rbField = QuotaBalanceCmd.class.getDeclaredField("_responseBuilder"); - rbField.setAccessible(true); - rbField.set(cmd, responseBuilder); + @InjectMocks + @Spy + QuotaBalanceCmd quotaBalanceCmdSpy; - List quotaBalanceVOList = new ArrayList(); - Mockito.when(responseBuilder.getQuotaBalance(Mockito.any(cmd.getClass()))).thenReturn(quotaBalanceVOList); - Mockito.when(responseBuilder.createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse()); - Mockito.when(responseBuilder.createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(new QuotaBalanceResponse()); - Mockito.lenient().when(responseBuilder.startOfNextDay(Mockito.any(Date.class))).thenReturn(new Date()); + @Test + public void executeTestSetResponseObject() { + QuotaBalanceResponse expected = new QuotaBalanceResponse(); + Mockito.doReturn(expected).when(quotaResponseBuilderMock).createQuotaBalanceResponse(Mockito.eq(quotaBalanceCmdSpy)); - // end date not specified - cmd.setStartDate(new Date()); - cmd.setEndDate(null); - cmd.execute(); - Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaLastBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class)); - Mockito.verify(responseBuilder, Mockito.times(0)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class)); + quotaBalanceCmdSpy.execute(); - // end date specified - cmd.setEndDate(new Date()); - cmd.execute(); - Mockito.verify(responseBuilder, Mockito.times(1)).createQuotaBalanceResponse(Mockito.eq(quotaBalanceVOList), Mockito.any(Date.class), Mockito.any(Date.class)); + Assert.assertEquals(expected, quotaBalanceCmdSpy.getResponseObject()); } + } diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java index 81b4992082d9..0c7610461c15 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/api/response/QuotaResponseBuilderImplTest.java @@ -40,6 +40,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.QuotaBalanceCmd; import org.apache.cloudstack.api.command.QuotaConfigureEmailCmd; import org.apache.cloudstack.api.command.QuotaCreditsListCmd; import org.apache.cloudstack.api.command.QuotaEmailTemplateListCmd; @@ -249,7 +250,7 @@ public void testAddQuotaCredits() { credit.setCredit(new BigDecimal(amount)); Mockito.when(quotaCreditsDaoMock.saveCredits(Mockito.any(QuotaCreditsVO.class))).thenReturn(credit); - Mockito.when(quotaBalanceDaoMock.lastQuotaBalance(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(Date.class))).thenReturn(new BigDecimal(111)); + Mockito.when(quotaBalanceDaoMock.getLastQuotaBalance(Mockito.anyLong(), Mockito.anyLong())).thenReturn(new BigDecimal(111)); Mockito.doReturn(userVoMock).when(quotaResponseBuilderSpy).getCreditorForQuotaCredits(credit); AccountVO account = new AccountVO(); @@ -295,33 +296,6 @@ public void testUpdateQuotaEmailTemplate() { assertTrue(quotaResponseBuilderSpy.updateQuotaEmailTemplate(cmd)); } - @Test - public void testCreateQuotaLastBalanceResponse() { - List quotaBalance = new ArrayList<>(); - // null balance test - try { - quotaResponseBuilderSpy.createQuotaLastBalanceResponse(null, new Date()); - } catch (InvalidParameterValueException e) { - assertTrue(e.getMessage().equals("There are no balance entries on or before the requested date.")); - } - - // empty balance test - try { - quotaResponseBuilderSpy.createQuotaLastBalanceResponse(quotaBalance, new Date()); - } catch (InvalidParameterValueException e) { - assertTrue(e.getMessage().equals("There are no balance entries on or before the requested date.")); - } - - // valid balance test - QuotaBalanceVO entry = new QuotaBalanceVO(); - entry.setAccountId(2L); - entry.setCreditBalance(new BigDecimal(100)); - quotaBalance.add(entry); - quotaBalance.add(entry); - QuotaBalanceResponse resp = quotaResponseBuilderSpy.createQuotaLastBalanceResponse(quotaBalance, null); - assertTrue(resp.getStartQuota().compareTo(new BigDecimal(200)) == 0); - } - @Test public void testStartOfNextDayWithoutParameters() { Date nextDate = quotaResponseBuilderSpy.startOfNextDay(); @@ -919,6 +893,46 @@ public void injectUsageTypeVariablesTestReturnInjectedVariables() { Assert.assertTrue(formattedVariables.containsValue("zonename")); } + private List getQuotaBalancesForTest() { + List balances = new ArrayList<>(); + + QuotaBalanceVO balance = new QuotaBalanceVO(); + balance.setUpdatedOn(new Date()); + balance.setCreditBalance(BigDecimal.valueOf(-10.42)); + balances.add(balance); + + balance = new QuotaBalanceVO(); + balance.setUpdatedOn(new Date()); + balance.setCreditBalance(BigDecimal.valueOf(-18.94)); + balances.add(balance); + + balance = new QuotaBalanceVO(); + balance.setUpdatedOn(new Date()); + balance.setCreditBalance(BigDecimal.valueOf(-29.37)); + balances.add(balance); + + return balances; + } + + @Test + public void createQuotaBalancesResponseTestCreateResponse() { + List balances = getQuotaBalancesForTest(); + + QuotaBalanceResponse expected = new QuotaBalanceResponse(); + expected.setObjectName("balance"); + expected.setCurrency("$"); + + Mockito.doReturn(balances).when(quotaServiceMock).listQuotaBalancesForAccount(Mockito.any(), Mockito.any(), Mockito.any()); + QuotaBalanceResponse result = quotaResponseBuilderSpy.createQuotaBalanceResponse(new QuotaBalanceCmd()); + + Assert.assertEquals(expected.getCurrency(), result.getCurrency()); + + for (int i = 0; i < balances.size(); i++) { + Assert.assertEquals(balances.get(i).getUpdatedOn(), result.getBalances().get(i).getDate()); + Assert.assertEquals(balances.get(i).getCreditBalance(), result.getBalances().get(i).getBalance()); + } + } + @Test public void createDummyRecordForEachQuotaTypeIfUsageTypeIsNotInformedTestUsageTypeDifferentFromNullDoNothing() { List listUsage = new ArrayList<>(); diff --git a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java index a0fe63de8518..89c48b1cc660 100644 --- a/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java +++ b/plugins/database/quota/src/test/java/org/apache/cloudstack/quota/QuotaServiceImplTest.java @@ -18,6 +18,7 @@ import com.cloud.configuration.Config; import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.utils.db.TransactionLegacy; @@ -31,7 +32,9 @@ import org.apache.cloudstack.quota.dao.QuotaUsageJoinDao; import org.apache.cloudstack.quota.vo.QuotaAccountVO; import org.apache.cloudstack.quota.vo.QuotaBalanceVO; +import org.apache.commons.lang3.time.DateUtils; import org.joda.time.DateTime; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -43,7 +46,6 @@ import javax.naming.ConfigurationException; import java.lang.reflect.Field; -import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -51,6 +53,8 @@ @RunWith(MockitoJUnitRunner.class) public class QuotaServiceImplTest extends TestCase { + @Mock + AccountVO accountVoMock; @Mock AccountDao accountDaoMock; @Mock @@ -67,9 +71,6 @@ public class QuotaServiceImplTest extends TestCase { QuotaUsageJoinDao quotaUsageJoinDaoMock; @Mock QuotaResponseBuilder respBldr; - @Mock - private AccountVO accountVoMock; - @Spy @InjectMocks QuotaServiceImpl quotaServiceImplSpy; @@ -112,30 +113,6 @@ public void setup() throws IllegalAccessException, NoSuchFieldException, Configu quotaServiceImplSpy.configure("randomName", null); } - @Test - public void testFindQuotaBalanceVO() { - final long accountId = 2L; - final String accountName = "admin123"; - final long domainId = 1L; - final Date startDate = new DateTime().minusDays(2).toDate(); - final Date endDate = new Date(); - - List records = new ArrayList<>(); - QuotaBalanceVO qb = new QuotaBalanceVO(); - qb.setCreditBalance(new BigDecimal(100)); - qb.setAccountId(accountId); - records.add(qb); - - Mockito.when(respBldr.startOfNextDay(Mockito.any(Date.class))).thenReturn(startDate); - Mockito.when(quotaBalanceDao.findQuotaBalance(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class), Mockito.any(Date.class))).thenReturn(records); - Mockito.when(quotaBalanceDao.lastQuotaBalanceVO(Mockito.eq(accountId), Mockito.eq(domainId), Mockito.any(Date.class))).thenReturn(records); - - // with enddate - assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, endDate).get(0).equals(qb)); - // without enddate - assertTrue(quotaServiceImplSpy.findQuotaBalanceVO(accountId, accountName, domainId, startDate, null).get(0).equals(qb)); - } - @Test public void testGetQuotaUsage() { final long accountId = 2L; @@ -182,4 +159,66 @@ public void testSetMinBalance() { Mockito.verify(quotaAcc, Mockito.times(1)).persistQuotaAccount(Mockito.any(QuotaAccountVO.class)); } + @Test(expected = InvalidParameterValueException.class) + public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsNullAndEndDateIsNotNullThrowsInvalidParameterException() { + quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(null, new Date()); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsAfterNowThrowsInvalidParameterValueException() { + Date startDate = DateUtils.addMinutes(new Date(), 1); + quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, null); + } + + @Test + public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestEndDateIsAfterNowDoesNothing() { + Date startDate = DateUtils.addMinutes(new Date(), -1); + Date endDate = DateUtils.addMinutes(new Date(), 1); + quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, endDate); + } + + @Test(expected = InvalidParameterValueException.class) + public void validateStartDateAndEndDateForListQuotaBalancesForAccountTestStartDateIsAfterEndDateThrowsInvalidParameterValueException() { + Date startDate = DateUtils.addMinutes(new Date(), -10); + Date endDate = DateUtils.addMinutes(new Date(), -15); + quotaServiceImplSpy.validateStartDateAndEndDateForListQuotaBalancesForAccount(startDate, endDate); + } + + @Test + public void listQuotaBalancesForAccountTestLastQuotaBalanceIsNullReturnsEmptyList() { + Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(), Mockito.any()); + Mockito.doReturn(null).when(quotaBalanceDao).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); + + List result = quotaServiceImplSpy.listQuotaBalancesForAccount(1L, null, null); + + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void listQuotaBalancesForAccountTestLastQuotaBalanceIsNotNullReturnsIt() { + QuotaBalanceVO expected = new QuotaBalanceVO(); + + Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(), Mockito.any()); + Mockito.doReturn(expected).when(quotaBalanceDao).getLastQuotaBalanceEntry(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); + Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); + + List result = quotaServiceImplSpy.listQuotaBalancesForAccount(1L, null, null); + + Assert.assertEquals(expected, result.get(0)); + } + + @Test + public void listQuotaBalancesForAccountTestReturnsQuotaBalances() { + List expected = new ArrayList<>(); + + Mockito.doNothing().when(quotaServiceImplSpy).validateStartDateAndEndDateForListQuotaBalancesForAccount(Mockito.any(), Mockito.any()); + Mockito.doReturn(expected).when(quotaBalanceDao).listQuotaBalances(Mockito.anyLong(), Mockito.anyLong(), Mockito.any(), Mockito.any()); + Mockito.doReturn(Mockito.mock(AccountVO.class)).when(accountDaoMock).findByIdIncludingRemoved(Mockito.anyLong()); + + List result = quotaServiceImplSpy.listQuotaBalancesForAccount(1L, new Date(), null); + + Assert.assertEquals(expected, result); + } + } diff --git a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java index ac840c00be32..3f2d85458d30 100644 --- a/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java +++ b/plugins/event-bus/webhook/src/main/java/org/apache/cloudstack/mom/webhook/WebhookDeliveryThread.java @@ -48,6 +48,8 @@ import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; +import javax.net.ssl.SSLContext; + import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.TrustAllStrategy; import org.apache.http.entity.ContentType; @@ -97,7 +99,9 @@ protected boolean isValidJson(String json) { protected void setHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (webhook.isSslVerification()) { - httpClient = HttpClients.createDefault(); + httpClient = HttpClients.custom() + .setSSLContext(SSLContext.getDefault()) + .build(); return; } httpClient = HttpClients diff --git a/plugins/host-allocators/random/pom.xml b/plugins/host-allocators/random/pom.xml deleted file mode 100644 index 061caf1573ff..000000000000 --- a/plugins/host-allocators/random/pom.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - 4.0.0 - cloud-plugin-host-allocator-random - Apache CloudStack Plugin - Host Allocator Random - - org.apache.cloudstack - cloudstack-plugins - 4.23.0.0-SNAPSHOT - ../../pom.xml - - diff --git a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java deleted file mode 100644 index 42129944a194..000000000000 --- a/plugins/host-allocators/random/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java +++ /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. -package com.cloud.agent.manager.allocator.impl; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import javax.inject.Inject; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.ListUtils; -import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; - -import com.cloud.agent.manager.allocator.HostAllocator; -import com.cloud.capacity.CapacityManager; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.deploy.DeploymentPlan; -import com.cloud.deploy.DeploymentPlanner.ExcludeList; -import com.cloud.host.Host; -import com.cloud.host.Host.Type; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; -import com.cloud.offering.ServiceOffering; -import com.cloud.resource.ResourceManager; -import com.cloud.storage.VMTemplateVO; -import com.cloud.utils.Pair; -import com.cloud.utils.component.AdapterBase; -import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineProfile; - -@Component -public class RandomAllocator extends AdapterBase implements HostAllocator { - @Inject - private HostDao _hostDao; - @Inject - private ResourceManager _resourceMgr; - @Inject - private ClusterDao clusterDao; - @Inject - private ClusterDetailsDao clusterDetailsDao; - @Inject - private CapacityManager capacityManager; - - protected List listHostsByTags(Host.Type type, long dcId, Long podId, Long clusterId, String offeringHostTag, String templateTag) { - List taggedHosts = new ArrayList<>(); - if (offeringHostTag != null) { - taggedHosts.addAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, offeringHostTag)); - } - if (templateTag != null) { - List templateTaggedHosts = _hostDao.listByHostTag(type, clusterId, podId, dcId, templateTag); - if (taggedHosts.isEmpty()) { - taggedHosts = templateTaggedHosts; - } else { - taggedHosts.retainAll(templateTaggedHosts); - } - } - if (logger.isDebugEnabled()) { - logger.debug(String.format("Found %d hosts %s with type: %s, zone ID: %d, pod ID: %d, cluster ID: %s, offering host tag(s): %s, template tag: %s", - taggedHosts.size(), - (taggedHosts.isEmpty() ? "" : String.format("(%s)", StringUtils.join(taggedHosts.stream().map(HostVO::toString).toArray(), ","))), - type.name(), dcId, podId, clusterId, offeringHostTag, templateTag)); - } - return taggedHosts; - } - private List findSuitableHosts(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, - ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - long dcId = plan.getDataCenterId(); - Long podId = plan.getPodId(); - Long clusterId = plan.getClusterId(); - ServiceOffering offering = vmProfile.getServiceOffering(); - List hostsCopy = null; - List suitableHosts = new ArrayList<>(); - - if (type == Host.Type.Storage) { - return suitableHosts; - } - String offeringHostTag = offering.getHostTag(); - - VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); - String templateTag = template.getTemplateTag(); - String hostTag = null; - if (ObjectUtils.anyNotNull(offeringHostTag, templateTag)) { - hostTag = ObjectUtils.allNotNull(offeringHostTag, templateTag) ? - String.format("%s, %s", offeringHostTag, templateTag) : - ObjectUtils.firstNonNull(offeringHostTag, templateTag); - logger.debug("Looking for hosts in dc [{}], pod [{}], cluster [{}] and complying with host tag(s): [{}]", dcId, podId, clusterId, hostTag); - } else { - logger.debug("Looking for hosts in dc: {} pod: {} cluster: {}", dcId , podId, clusterId); - } - if (hosts != null) { - // retain all computing hosts, regardless of whether they support routing...it's random after all - hostsCopy = new ArrayList<>(hosts); - if (ObjectUtils.anyNotNull(offeringHostTag, templateTag)) { - hostsCopy.retainAll(listHostsByTags(type, dcId, podId, clusterId, offeringHostTag, templateTag)); - } else { - hostsCopy.retainAll(_hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId)); - } - } else { - // list all computing hosts, regardless of whether they support routing...it's random after all - if (offeringHostTag != null) { - hostsCopy = listHostsByTags(type, dcId, podId, clusterId, offeringHostTag, templateTag); - } else { - hostsCopy = _hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId); - } - } - hostsCopy = ListUtils.union(hostsCopy, _hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(offeringHostTag)); - - if (hostsCopy.isEmpty()) { - logger.info("No suitable host found for VM [{}] in {}.", vmProfile, hostTag); - return null; - } - - logger.debug("Random Allocator found {} hosts", hostsCopy.size()); - if (hostsCopy.isEmpty()) { - return suitableHosts; - } - - Collections.shuffle(hostsCopy); - for (Host host : hostsCopy) { - if (suitableHosts.size() == returnUpTo) { - break; - } - if (avoid.shouldAvoid(host)) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Host %s is in avoid set, skipping this and trying other available hosts", host)); - } - continue; - } - Pair cpuCapabilityAndCapacity = capacityManager.checkIfHostHasCpuCapabilityAndCapacity(host, offering, considerReservedCapacity); - if (!cpuCapabilityAndCapacity.first() || !cpuCapabilityAndCapacity.second()) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Not using host %s; host has cpu capability? %s, host has capacity? %s", host, cpuCapabilityAndCapacity.first(), cpuCapabilityAndCapacity.second())); - } - continue; - } - if (logger.isDebugEnabled()) { - logger.debug(String.format("Found a suitable host, adding to list: %s", host)); - } - suitableHosts.add(host); - } - if (logger.isDebugEnabled()) { - logger.debug("Random Host Allocator returning " + suitableHosts.size() + " suitable hosts"); - } - return suitableHosts; - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, - ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - if (CollectionUtils.isEmpty(hosts)) { - if (logger.isDebugEnabled()) { - logger.debug("Random Allocator found 0 hosts as given host list is empty"); - } - return new ArrayList<>(); - } - return findSuitableHosts(vmProfile, plan, type, avoid, hosts, returnUpTo, considerReservedCapacity); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, - Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { - return findSuitableHosts(vmProfile, plan, type, avoid, null, returnUpTo, considerReservedCapacity); - } - - @Override - public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - // currently we do no special checks to rule out a VM being upgradable to an offering, so - // return true - return true; - } -} diff --git a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml b/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml deleted file mode 100644 index d84eaafaa5a7..000000000000 --- a/plugins/host-allocators/random/src/main/resources/META-INF/cloudstack/host-allocator-random/spring-host-allocator-random-context.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - diff --git a/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java b/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java deleted file mode 100644 index 538d7157184a..000000000000 --- a/plugins/host-allocators/random/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java +++ /dev/null @@ -1,80 +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 com.cloud.agent.manager.allocator.impl; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.commons.collections.CollectionUtils; -import org.junit.Assert; -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.host.Host; -import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; - -@RunWith(MockitoJUnitRunner.class) -public class RandomAllocatorTest { - - @Mock - HostDao hostDao; - @InjectMocks - RandomAllocator randomAllocator; - - @Test - public void testListHostsByTags() { - Host.Type type = Host.Type.Routing; - Long id = 1L; - String templateTag = "tag1"; - String offeringTag = "tag2"; - HostVO host1 = Mockito.mock(HostVO.class); - HostVO host2 = Mockito.mock(HostVO.class); - Mockito.when(hostDao.listByHostTag(type, id, id, id, offeringTag)).thenReturn(List.of(host1, host2)); - - // No template tagged host - Mockito.when(hostDao.listByHostTag(type, id, id, id, templateTag)).thenReturn(new ArrayList<>()); - List result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, templateTag); - Assert.assertTrue(CollectionUtils.isEmpty(result)); - - // Different template tagged host - HostVO host3 = Mockito.mock(HostVO.class); - Mockito.when(hostDao.listByHostTag(type, id, id, id, templateTag)).thenReturn(List.of(host3)); - result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, templateTag); - Assert.assertTrue(CollectionUtils.isEmpty(result)); - - // Matching template tagged host - Mockito.when(hostDao.listByHostTag(type, id, id, id, templateTag)).thenReturn(List.of(host1)); - result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, templateTag); - Assert.assertFalse(CollectionUtils.isEmpty(result)); - Assert.assertEquals(1, result.size()); - - // No template tag - result = randomAllocator.listHostsByTags(type, id, id, id, offeringTag, null); - Assert.assertFalse(CollectionUtils.isEmpty(result)); - Assert.assertEquals(2, result.size()); - - // No offering tag - result = randomAllocator.listHostsByTags(type, id, id, id, null, templateTag); - Assert.assertFalse(CollectionUtils.isEmpty(result)); - Assert.assertEquals(1, result.size()); - } -} 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 3d79b9efdd13..4e44d8cb7359 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 @@ -41,15 +41,15 @@ public class HypervInvestigator extends AdapterBase implements Investigator { @Override public boolean isVmAlive(com.cloud.vm.VirtualMachine vm, Host host) throws UnknownVM { - Status status = isAgentAlive(host); + Status status = getHostAgentStatus(host); if (status == null) { throw new UnknownVM(); } - return status == Status.Up ? true : null; + return status == Status.Up; } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { if (agent.getHypervisorType() != Hypervisor.HypervisorType.Hyperv) { return null; } 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 ce9fbe6c232c..da9a0d6e2919 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 @@ -19,10 +19,7 @@ package com.cloud.ha; import com.cloud.agent.AgentManager; -import com.cloud.agent.api.Answer; -import com.cloud.agent.api.CheckOnHostCommand; 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; @@ -34,11 +31,12 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.ha.HAManager; +import org.apache.cloudstack.kvm.ha.KVMHostActivityChecker; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import javax.inject.Inject; -import java.util.Arrays; +import java.util.Collections; import java.util.List; public class KVMInvestigator extends AdapterBase implements Investigator { @@ -54,13 +52,15 @@ public class KVMInvestigator extends AdapterBase implements Investigator { private HAManager haManager; @Inject private DataStoreProviderManager dataStoreProviderMgr; + @Inject + private KVMHostActivityChecker hostActivityChecker; @Override public boolean isVmAlive(com.cloud.vm.VirtualMachine vm, Host host) throws UnknownVM { if (haManager.isHAEligible(host)) { return haManager.isVMAliveOnHost(host); } - Status status = isAgentAlive(host); + Status status = getHostAgentStatus(host); logger.debug("HA: HOST is ineligible legacy state {} for host {}", status, host); if (status == null) { throw new UnknownVM(); @@ -73,86 +73,41 @@ public boolean isVmAlive(com.cloud.vm.VirtualMachine vm, Host host) throws Unkno } @Override - public Status isAgentAlive(Host agent) { - if (agent.getHypervisorType() != Hypervisor.HypervisorType.KVM && agent.getHypervisorType() != Hypervisor.HypervisorType.LXC) { + public Status getHostAgentStatus(Host host) { + if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) { return null; } - if (haManager.isHAEligible(agent)) { - return haManager.getHostStatus(agent); + if (haManager.isHAEligible(host)) { + return haManager.getHostStatusFromHAConfig(host); } - List clusterPools = _storagePoolDao.findPoolsInClusters(Arrays.asList(agent.getClusterId()), null); - boolean storageSupportHA = storageSupportHa(clusterPools); - if (!storageSupportHA) { - List zonePools = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(agent.getDataCenterId(), agent.getHypervisorType()); - storageSupportHA = storageSupportHa(zonePools); + List clusterPools = _storagePoolDao.findPoolsInClusters(Collections.singletonList(host.getClusterId()), null); + boolean storageSupportsHA = storageSupportsHA(clusterPools); + if (!storageSupportsHA) { + List zonePools = _storagePoolDao.findZoneWideStoragePoolsByHypervisor(host.getDataCenterId(), host.getHypervisorType()); + storageSupportsHA = storageSupportsHA(zonePools); } - if (!storageSupportHA) { - logger.warn("Agent investigation was requested on host {}, but host does not support investigation because it has no NFS storage. Skipping investigation.", agent); + if (!storageSupportsHA) { + logger.warn("Agent investigation was requested on host {}, but host does not support investigation" + + " because it has no HA supported storage. Skipping investigation.", host); return null; } - Status hostStatus = null; - Status neighbourStatus = null; - boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value(); - CheckOnHostCommand cmd = new CheckOnHostCommand(agent, reportFailureIfOneStorageIsDown); - - try { - Answer answer = _agentMgr.easySend(agent.getId(), cmd); - if (answer != null) { - hostStatus = answer.getResult() ? Status.Down : Status.Up; - } - } catch (Exception e) { - logger.debug("Failed to send command to host: {}", agent); - } - if (hostStatus == null) { - hostStatus = Status.Disconnected; - } - - List neighbors = _resourceMgr.listHostsInClusterByStatus(agent.getClusterId(), Status.Up); - for (HostVO neighbor : neighbors) { - if (neighbor.getId() == agent.getId() - || (neighbor.getHypervisorType() != Hypervisor.HypervisorType.KVM && neighbor.getHypervisorType() != Hypervisor.HypervisorType.LXC)) { - continue; - } - logger.debug("Investigating host:{} via neighbouring host:{}", agent, neighbor); - try { - Answer answer = _agentMgr.easySend(neighbor.getId(), cmd); - if (answer != null) { - neighbourStatus = answer.getResult() ? Status.Down : Status.Up; - logger.debug("Neighbouring host:{} returned status:{} for the investigated host:{}", neighbor, neighbourStatus, agent); - if (neighbourStatus == Status.Up) { - break; - } - } - } catch (Exception e) { - logger.debug("Failed to send command to host: {}", neighbor); - } - } - if (neighbourStatus == Status.Up && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) { - hostStatus = Status.Disconnected; - } - if (neighbourStatus == Status.Down && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) { - hostStatus = Status.Down; - } - logger.debug("HA: HOST is ineligible legacy state {} for host {}", hostStatus, agent); - return hostStatus; + return hostActivityChecker.getHostAgentStatus(host); } - private boolean storageSupportHa(List pools) { - boolean storageSupportHA = false; + private boolean storageSupportsHA(List pools) { for (StoragePoolVO pool : pools) { DataStoreProvider storeProvider = dataStoreProviderMgr.getDataStoreProvider(pool.getStorageProviderName()); DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); if (storeDriver instanceof PrimaryDataStoreDriver) { PrimaryDataStoreDriver primaryStoreDriver = (PrimaryDataStoreDriver)storeDriver; if (primaryStoreDriver.isStorageSupportHA(pool.getPoolType())) { - storageSupportHA = true; - break; + return true; } } } - return storageSupportHA; + return false; } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java index e9a7ac8951ce..f30e2c779195 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java @@ -35,10 +35,9 @@ public class KVMHABase { protected Logger logger = LogManager.getLogger(getClass()); private long _timeout = 60000; /* 1 minutes */ - protected long _heartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT); - protected long _heartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY); + protected long _heartBeatUpdateFreqInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY); protected long _heartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES); - protected long _heartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP); + protected long _heartBeatUpdateRetrySleepInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP); public static enum PoolType { PrimaryStorage, SecondaryStorage @@ -138,7 +137,7 @@ protected String checkingMountPoint(HAStoragePool pool, String poolName) { /* Can't find the mount point? */ /* we need to mount it under poolName */ if (poolName != null) { - Script mount = new Script("/bin/bash", 60000); + Script mount = new Script("/bin/bash", _timeout); mount.add("-c"); mount.add("mount " + mountSource + " " + destPath); String result = mount.execute(); @@ -154,7 +153,6 @@ protected String checkingMountPoint(HAStoragePool pool, String poolName) { } protected String getMountPoint(HAStoragePool storagePool) { - StoragePool pool = null; String poolName = null; try { @@ -171,7 +169,6 @@ protected String getMountPoint(HAStoragePool storagePool) { } poolName = pool.getName(); } - } catch (LibvirtException e) { logger.debug("Ignoring libvirt error.", e); } finally { @@ -234,7 +231,7 @@ protected String runScriptRetry(String cmdString, OutputInterpreter interpreter) return result; } - public Boolean checkingHeartBeat() { + public Boolean hasHeartBeat() { // TODO Auto-generated method stub return null; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java index db6190fa8f28..0ee59c95da39 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java @@ -26,44 +26,43 @@ public class KVMHAChecker extends KVMHABase implements Callable { private List storagePools; private HostTO host; - private boolean reportFailureIfOneStorageIsDown; + private boolean reportIfHeartBeatFailedForOneStoragePool; - public KVMHAChecker(List pools, HostTO host, boolean reportFailureIfOneStorageIsDown) { + public KVMHAChecker(List pools, HostTO host, boolean reportIfHeartBeatFailedForOneStoragePool) { this.storagePools = pools; this.host = host; - this.reportFailureIfOneStorageIsDown = reportFailureIfOneStorageIsDown; + this.reportIfHeartBeatFailedForOneStoragePool = reportIfHeartBeatFailedForOneStoragePool; } /* - * True means heartbeaing is on going, or we can't get it's status. False - * means heartbeating is stopped definitely + * True means heart beating is on going, or we can't get it's status. + * False means heart beating is stopped definitely. */ @Override - public Boolean checkingHeartBeat() { - boolean validResult = false; - - String hostAndPools = String.format("host IP [%s] in pools [%s]", host.getPrivateNetwork().getIp(), storagePools.stream().map(pool -> pool.getPoolUUID()).collect(Collectors.joining(", "))); - - logger.debug(String.format("Checking heart beat with KVMHAChecker for %s", hostAndPools)); + public Boolean hasHeartBeat() { + String hostAndPools = String.format("host IP [%s] in pools [%s]", host.getPrivateNetwork().getIp(), + storagePools.stream().map(pool -> pool.getPoolUUID()).collect(Collectors.joining(", "))); + logger.debug("Checking heart beat with KVMHAChecker for {}", hostAndPools); + boolean heartBeatCheckResult = false; for (HAStoragePool pool : storagePools) { - validResult = pool.getPool().checkingHeartBeat(pool, host); - if (reportFailureIfOneStorageIsDown && !validResult) { + heartBeatCheckResult = pool.getPool().hasHeartBeat(pool, host); + if (reportIfHeartBeatFailedForOneStoragePool && !heartBeatCheckResult) { break; } } - if (!validResult) { - logger.warn(String.format("All checks with KVMHAChecker for %s considered it as dead. It may cause a shutdown of the host.", hostAndPools)); + if (!heartBeatCheckResult) { + logger.warn("All checks with KVMHAChecker for {} considered it as dead. It may cause a shutdown of the host.", hostAndPools); } - return validResult; + return heartBeatCheckResult; } @Override public Boolean call() throws Exception { // logger.addAppender(new org.apache.log4j.ConsoleAppender(new // org.apache.log4j.PatternLayout(), "System.out")); - return checkingHeartBeat(); + return hasHeartBeat(); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java index aa868ff1d3f2..9f1b849e9727 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java @@ -34,53 +34,49 @@ public class KVMHAMonitor extends KVMHABase implements Runnable { - private final Map storagePool = new ConcurrentHashMap<>(); + private final Map haStoragePools = new ConcurrentHashMap<>(); private final boolean rebootHostAndAlertManagementOnHeartbeatTimeout; private final String hostPrivateIp; - public KVMHAMonitor(HAStoragePool pool, String host) { - if (pool != null) { - storagePool.put(pool.getPoolUUID(), pool); - } + public KVMHAMonitor(String host) { hostPrivateIp = host; - rebootHostAndAlertManagementOnHeartbeatTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.REBOOT_HOST_AND_ALERT_MANAGEMENT_ON_HEARTBEAT_TIMEOUT); } public void addStoragePool(HAStoragePool pool) { - synchronized (storagePool) { - storagePool.put(pool.getPoolUUID(), pool); + synchronized (haStoragePools) { + haStoragePools.put(pool.getPoolUUID(), pool); } } public void removeStoragePool(String uuid) { - synchronized (storagePool) { - HAStoragePool pool = storagePool.get(uuid); + synchronized (haStoragePools) { + HAStoragePool pool = haStoragePools.get(uuid); if (pool != null) { Script.runSimpleBashScript("umount " + pool.getMountDestPath()); - storagePool.remove(uuid); + haStoragePools.remove(uuid); } } } public List getStoragePools() { - synchronized (storagePool) { - return new ArrayList<>(storagePool.values()); + synchronized (haStoragePools) { + return new ArrayList<>(haStoragePools.values()); } } public HAStoragePool getStoragePool(String uuid) { - synchronized (storagePool) { - return storagePool.get(uuid); + synchronized (haStoragePools) { + return haStoragePools.get(uuid); } } protected void runHeartBeat() { - synchronized (storagePool) { + synchronized (haStoragePools) { Set removedPools = new HashSet<>(); - for (String uuid : storagePool.keySet()) { - HAStoragePool primaryStoragePool = storagePool.get(uuid); + for (String uuid : haStoragePools.keySet()) { + HAStoragePool primaryStoragePool = haStoragePools.get(uuid); if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(primaryStoragePool.getPool().getType())) { checkForNotExistingLibvirtStoragePools(removedPools, uuid); if (removedPools.contains(uuid)) { @@ -91,7 +87,7 @@ protected void runHeartBeat() { result = executePoolHeartBeatCommand(uuid, primaryStoragePool, result); if (result != null && rebootHostAndAlertManagementOnHeartbeatTimeout) { - logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; stopping cloudstack-agent.", uuid, result)); + logger.warn("Write heartbeat for pool [{}] failed: {}; stopping cloudstack-agent.", uuid, result); primaryStoragePool.getPool().createHeartBeatCommand(primaryStoragePool, null, false);; } } @@ -104,20 +100,18 @@ protected void runHeartBeat() { } private String executePoolHeartBeatCommand(String uuid, HAStoragePool primaryStoragePool, String result) { - for (int i = 1; i <= _heartBeatUpdateMaxTries; i++) { + for (int attempt = 1; attempt <= _heartBeatUpdateMaxTries; attempt++) { result = primaryStoragePool.getPool().createHeartBeatCommand(primaryStoragePool, hostPrivateIp, true); - - if (result != null) { - logger.warn(String.format("Write heartbeat for pool [%s] failed: %s; try: %s of %s.", uuid, result, i, _heartBeatUpdateMaxTries)); - try { - Thread.sleep(_heartBeatUpdateRetrySleep); - } catch (InterruptedException e) { - logger.debug("[IGNORED] Interrupted between heartbeat retries.", e); - } - } else { + if (result == null) { break; } + logger.warn("Write heartbeat for pool [{}] failed: {}; try: {} of {}.", uuid, result, attempt, _heartBeatUpdateMaxTries); + try { + Thread.sleep(_heartBeatUpdateRetrySleepInMs); + } catch (InterruptedException e) { + logger.debug("[IGNORED] Interrupted between heartbeat retries.", e); + } } return result; } @@ -128,21 +122,21 @@ private void checkForNotExistingLibvirtStoragePools(Set removedPools, St StoragePool storage = conn.storagePoolLookupByUUIDString(uuid); if (storage == null || storage.getInfo().state != StoragePoolState.VIR_STORAGE_POOL_RUNNING) { if (storage == null) { - logger.debug(String.format("Libvirt storage pool [%s] not found, removing from HA list.", uuid)); + logger.debug("Libvirt storage pool [{}] not found, removing from HA list.", uuid); } else { - logger.debug(String.format("Libvirt storage pool [%s] found, but not running, removing from HA list.", uuid)); + logger.debug("Libvirt storage pool [{}] found, but not running, removing from HA list.", uuid); } removedPools.add(uuid); } - logger.debug(String.format("Found NFS storage pool [%s] in libvirt, continuing.", uuid)); + logger.debug("Found NFS storage pool [{}] in libvirt, continuing.", uuid); } catch (LibvirtException e) { - logger.debug(String.format("Failed to lookup libvirt storage pool [%s].", uuid), e); + logger.debug("Failed to lookup libvirt storage pool [{}].", uuid, e); if (e.toString().contains("pool not found")) { - logger.debug(String.format("Removing pool [%s] from HA monitor since it was deleted.", uuid)); + logger.debug("Removing pool [{}] from HA monitor since it was deleted.", uuid); removedPools.add(uuid); } } @@ -155,11 +149,10 @@ public void run() { runHeartBeat(); try { - Thread.sleep(_heartBeatUpdateFreq); + Thread.sleep(_heartBeatUpdateFreqInMs); } catch (InterruptedException e) { logger.debug("[IGNORED] Interrupted between heartbeats.", e); } } } - } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java index e6937b515e95..c13be64a3a3e 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAVMActivityChecker.java @@ -39,12 +39,12 @@ public KVMHAVMActivityChecker(final HAStoragePool pool, final HostTO host, final } @Override - public Boolean checkingHeartBeat() { - return this.storagePool.getPool().vmActivityCheck(storagePool, host, activityScriptTimeout, volumeUuidList, vmActivityCheckPath, suspectTimeInSeconds); + public Boolean hasHeartBeat() { + return this.storagePool.getPool().hasVmActivity(storagePool, host, activityScriptTimeout, volumeUuidList, vmActivityCheckPath, suspectTimeInSeconds); } @Override public Boolean call() throws Exception { - return checkingHeartBeat(); + return hasHeartBeat(); } } 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 0f0f80c0c17d..06b668b801d3 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 @@ -124,6 +124,7 @@ import org.libvirt.LibvirtException; import org.libvirt.MemoryStatistic; import org.libvirt.Network; +import org.libvirt.SchedLongParameter; import org.libvirt.SchedParameter; import org.libvirt.SchedUlongParameter; import org.libvirt.Secret; @@ -883,6 +884,25 @@ protected enum HealthCheckResult { SUCCESS, FAILURE, IGNORE } + public enum CpuSchedulerParameter { + CPU_SHARES("cpu_shares"), PERIOD("vcpu_period"), QUOTA("vcpu_quota"); + + private String name; + + CpuSchedulerParameter(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getName(); + } + } + protected BridgeType bridgeType; protected StorageSubsystemCommandHandler storageHandler; @@ -1388,9 +1408,9 @@ public boolean configure(final String name, final Map params) th final String[] info = NetUtils.getNetworkParams(privateNic); - kvmhaMonitor = new KVMHAMonitor(null, info[0]); - final Thread ha = new Thread(kvmhaMonitor); - ha.start(); + kvmhaMonitor = new KVMHAMonitor(info[0]); + final Thread haMonitorThread = new Thread(kvmhaMonitor); + haMonitorThread.start(); storagePoolManager = new KVMStoragePoolManager(storageLayer, kvmhaMonitor); @@ -2965,23 +2985,61 @@ protected String getUuid(String uuid) { protected void setQuotaAndPeriod(VirtualMachineTO vmTO, CpuTuneDef ctd) { if (vmTO.isLimitCpuUse() && vmTO.getCpuQuotaPercentage() != null) { Double cpuQuotaPercentage = vmTO.getCpuQuotaPercentage(); - int period = CpuTuneDef.DEFAULT_PERIOD; - int quota = (int) (period * cpuQuotaPercentage); - if (quota < CpuTuneDef.MIN_QUOTA) { - LOGGER.info("Calculated quota (" + quota + ") below the minimum (" + CpuTuneDef.MIN_QUOTA + ") for VM domain " + vmTO.getUuid() + ", setting it to minimum " + - "and calculating period instead of using the default"); - quota = CpuTuneDef.MIN_QUOTA; - period = (int) ((double) quota / cpuQuotaPercentage); - if (period > CpuTuneDef.MAX_PERIOD) { - LOGGER.info("Calculated period (" + period + ") exceeds the maximum (" + CpuTuneDef.MAX_PERIOD + - "), setting it to the maximum"); - period = CpuTuneDef.MAX_PERIOD; - } + Pair periodAndQuota = getPeriodAndQuota(cpuQuotaPercentage); + ctd.setPeriod(periodAndQuota.first()); + ctd.setQuota(periodAndQuota.second()); + LOGGER.info("Setting quota = [{}] and period = [{}] to VM domain [{}].", periodAndQuota.second(), periodAndQuota.first(), vmTO.getUuid()); + } + } + + /** + * Calculates the CPU period and quota based on the quota percentage defined by the Management Server + * @param cpuQuotaPercentage CPU quota percentage defined by the Management Server + * @return The period and quota to be defined for the VM's domain + */ + protected Pair getPeriodAndQuota(double cpuQuotaPercentage) { + int period = CpuTuneDef.DEFAULT_PERIOD; + long quota = (long) (period * cpuQuotaPercentage); + if (quota < CpuTuneDef.MIN_QUOTA) { + LOGGER.info("Calculated quota ({}) below the minimum ({}), setting it to minimum and calculating period instead of using the default", quota, CpuTuneDef.MIN_QUOTA); + quota = CpuTuneDef.MIN_QUOTA; + period = (int) ((double) quota / cpuQuotaPercentage); + if (period > CpuTuneDef.MAX_PERIOD) { + LOGGER.info("Calculated period ({}) exceeds the maximum ({}), setting it to the maximum", period, CpuTuneDef.MAX_PERIOD); + period = CpuTuneDef.MAX_PERIOD; } - ctd.setQuota(quota); - ctd.setPeriod(period); - LOGGER.info("Setting quota=" + quota + ", period=" + period + " to VM domain " + vmTO.getUuid()); } + + LOGGER.info("Calculated period = [{}] and quota = [{}] given the [{}] quota percentage.", period, quota, cpuQuotaPercentage); + return new Pair<>(period, quota); + } + + /** + * Dynamically updates the domain's "vcpu_quota" and "period" fields of the CPU tune definition. + * This is required because the values of the fields must change according to the new CPU speed of the VM. + * When the CPU limitation is removed from the domain, the "vcpu_quota" field is set to 17,592,186,044,415. + * @param domain VM's domain. + * @param vmTO VM's transfer object, which contains the required fields to update the "vcpu_quota" and "period" fields. + * @param limitCpuUseChange Indicates whether the CPU limitation for the VM has changed. + * @throws org.libvirt.LibvirtException + **/ + public void updateCpuQuotaAndPeriod(Domain domain, VirtualMachineTO vmTO, boolean limitCpuUseChange) throws LibvirtException { + if (hypervisorLibvirtVersion < MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE || (!limitCpuUseChange && !vmTO.isLimitCpuUse())) { + logger.info("Not updating the [{}] and [{}] for the [{}] domain, because [{}].", + CpuSchedulerParameter.QUOTA, CpuSchedulerParameter.PERIOD, domain.getName(), hypervisorLibvirtVersion < MIN_LIBVIRT_VERSION_FOR_GUEST_CPU_TUNE ? + "the current Libvirt version does not support CPU tune" : "it was not requested to remove, change or apply CPU limitation for the instance."); + return; + } + + if (limitCpuUseChange && !vmTO.isLimitCpuUse()) { + logger.info("Updating the [{}] of the [{}] domain to [{}], because CPU limitation has been removed.", CpuSchedulerParameter.QUOTA, domain.getName(), CpuTuneDef.MAX_CPU_QUOTA); + LibvirtComputingResource.setQuota(domain, CpuTuneDef.MAX_CPU_QUOTA); + return; + } + + Pair periodAndQuota = getPeriodAndQuota(vmTO.getCpuQuotaPercentage()); + LibvirtComputingResource.setPeriod(domain, periodAndQuota.first()); + LibvirtComputingResource.setQuota(domain, periodAndQuota.second()); } protected void enlightenWindowsVm(VirtualMachineTO vmTO, FeaturesDef features) { @@ -3445,10 +3503,10 @@ protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO){ grd.setMemBalloning(!noMemBalloon); - Long maxRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam()); - - grd.setMemorySize(maxRam); - grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, maxRam)); + long requestedRam = ByteScaleUtils.bytesToKibibytes(vmTO.getRequestedRam()); + long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam()); + grd.setCurrentMem(getCurrentMemAccordingToMemBallooning(vmTO, requestedRam, minRam)); + grd.setMaxMemory(ByteScaleUtils.bytesToKibibytes(vmTO.getMaxRam())); int vcpus = vmTO.getCpus(); Integer maxVcpus = vmTO.getVcpuMaxLimit(); @@ -3459,18 +3517,19 @@ protected GuestResourceDef createGuestResourceDef(VirtualMachineTO vmTO){ return grd; } - protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long maxRam) { - long retVal = maxRam; + protected long getCurrentMemAccordingToMemBallooning(VirtualMachineTO vmTO, long requestedRam, long minRam) { if (noMemBalloon) { - LOGGER.warn(String.format("Setting VM's [%s] current memory as max memory [%s] due to memory ballooning is disabled. If you are using a custom service offering, verify if memory ballooning really should be disabled.", vmTO.toString(), maxRam)); - } else if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) { - LOGGER.warn(String.format("Setting System VM's [%s] current memory as max memory [%s].", vmTO.toString(), maxRam)); - } else { - long minRam = ByteScaleUtils.bytesToKibibytes(vmTO.getMinRam()); - LOGGER.debug(String.format("Setting VM's [%s] current memory as min memory [%s] due to memory ballooning is enabled.", vmTO.toString(), minRam)); - retVal = minRam; + LOGGER.warn("Setting VM's [{}] current memory as requested memory [{}] due to memory ballooning is disabled.", vmTO.toString(), requestedRam); + return requestedRam; + } + + if (vmTO != null && vmTO.getType() != VirtualMachine.Type.User) { + LOGGER.warn("Setting System VM's [{}] current memory as requested memory [{}].", vmTO.toString(), requestedRam); + return requestedRam; } - return retVal; + + LOGGER.debug("Setting VM's [{}] current memory as min memory [{}] due to memory ballooning is enabled.", vmTO.toString(), minRam); + return minRam; } /** @@ -6255,29 +6314,59 @@ public static long countDomainRunningVcpus(Domain dm) throws LibvirtException { **/ public static Integer getCpuShares(Domain dm) throws LibvirtException { for (SchedParameter c : dm.getSchedulerParameters()) { - if (c.field.equals("cpu_shares")) { + if (c.field.equals(CpuSchedulerParameter.CPU_SHARES.getName())) { return Integer.parseInt(c.getValueAsString()); } } - LOGGER.warn(String.format("Could not get cpu_shares of domain: [%s]. Returning default value of 0. ", dm.getName())); + LOGGER.warn("Could not get [{}] of domain: [{}]. Returning default value of 0. ", CpuSchedulerParameter.CPU_SHARES.getName(), dm.getName()); return 0; } /** - * Sets the cpu_shares (priority) of the running VM
+ * Updates the cpu_shares (priority) of the running VM. * @param dm domain of the VM. * @param cpuShares new priority of the running VM. - * @throws org.libvirt.LibvirtException **/ public static void setCpuShares(Domain dm, Integer cpuShares) throws LibvirtException { + LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.CPU_SHARES.getName(), dm.getName(), cpuShares); SchedUlongParameter[] params = new SchedUlongParameter[1]; params[0] = new SchedUlongParameter(); - params[0].field = "cpu_shares"; + params[0].field = CpuSchedulerParameter.CPU_SHARES.getName(); params[0].value = cpuShares; dm.setSchedulerParameters(params); } + /** + * Updates the period of the running VM. + * @param domain domain of the VM. + * @param period new period of the running VM. + **/ + public static void setPeriod(Domain domain, int period) throws LibvirtException { + LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.PERIOD.getName(), domain.getName(), period); + SchedUlongParameter[] params = new SchedUlongParameter[1]; + params[0] = new SchedUlongParameter(); + params[0].field = CpuSchedulerParameter.PERIOD.getName(); + params[0].value = period; + + domain.setSchedulerParameters(params); + } + + /** + * Updates the quota of the running VM. + * @param domain domain of the VM. + * @param quota new quota of the running VM. + **/ + public static void setQuota(Domain domain, long quota) throws LibvirtException { + LOGGER.info("Dynamically updating the [{}] of the [{}] VM to [{}].", CpuSchedulerParameter.QUOTA.getName(), domain.getName(), quota); + SchedLongParameter[] params = new SchedLongParameter[1]; + params[0] = new SchedLongParameter(); + params[0].field = CpuSchedulerParameter.QUOTA.getName(); + params[0].value = quota; + + domain.setSchedulerParameters(params); + } + /** * Set up a libvirt secret for a volume. If Libvirt says that a secret already exists for this volume path, we use its uuid. * The UUID of the secret needs to be prescriptive such that we can register the same UUID on target host during live migration diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java index 4d823783a99a..e114669b8b56 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java @@ -542,7 +542,7 @@ private void extractCpuTuneDef(final Element rootElement) { final String quota = getTagValue("quota", cpuTuneDefElement); if (StringUtils.isNotBlank(quota)) { - cpuTuneDef.setQuota((Integer.parseInt(quota))); + cpuTuneDef.setQuota((Long.parseLong(quota))); } final String period = getTagValue("period", cpuTuneDefElement); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java index 097a9b8dd322..bf8b1af6c18d 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java @@ -443,15 +443,15 @@ public String toString() { } public static class GuestResourceDef { - private long memory; + private long maxMemory; private long currentMemory = -1; private int vcpu = -1; private int maxVcpu = -1; private boolean memoryBalloning = false; private int memoryBalloonStatsPeriod = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.VM_MEMBALLOON_STATS_PERIOD); - public void setMemorySize(long mem) { - this.memory = mem; + public void setMaxMemory(long mem) { + this.maxMemory = mem; } public void setCurrentMem(long currMem) { @@ -484,8 +484,8 @@ public String toString() { response.append(String.format("%s\n", this.currentMemory)); response.append(String.format("%s\n", this.currentMemory)); - if (this.memory > this.currentMemory) { - response.append(String.format("%s\n", this.memory)); + if (this.maxMemory > this.currentMemory) { + response.append(String.format("%s\n", this.maxMemory)); response.append(String.format(" \n", this.maxVcpu - 1, this.currentMemory)); } @@ -1920,11 +1920,12 @@ public String toString() { public static class CpuTuneDef { private int _shares = 0; - private int quota = 0; + private long quota = 0; private int period = 0; static final int DEFAULT_PERIOD = 10000; static final int MIN_QUOTA = 1000; static final int MAX_PERIOD = 1000000; + public static final long MAX_CPU_QUOTA = 17592186044415L; public void setShares(int shares) { _shares = shares; @@ -1934,11 +1935,11 @@ public int getShares() { return _shares; } - public int getQuota() { + public long getQuota() { return quota; } - public void setQuota(int quota) { + public void setQuota(long quota) { this.quota = quota; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java index 48996a7ba97c..f901fd97ca76 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckOnHostCommandWrapper.java @@ -26,6 +26,7 @@ import java.util.concurrent.Future; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckOnHostAnswer; import com.cloud.agent.api.CheckOnHostCommand; import com.cloud.agent.api.to.HostTO; import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool; @@ -45,20 +46,21 @@ public Answer execute(final CheckOnHostCommand command, final LibvirtComputingRe final List pools = monitor.getStoragePools(); final HostTO host = command.getHost(); - final KVMHAChecker ha = new KVMHAChecker(pools, host, command.isCheckFailedOnOneStorage()); + + final KVMHAChecker ha = new KVMHAChecker(pools, host, command.shouldReportIfHeartBeatFailedForOneStoragePool()); final Future future = executors.submit(ha); try { - final Boolean result = future.get(); - if (result) { - return new Answer(command, false, "Heart is beating..."); + final Boolean hasHeartBeat = future.get(); + if (hasHeartBeat) { + return new CheckOnHostAnswer(command, true, "Heart is beating"); } else { - return new Answer(command); + return new CheckOnHostAnswer(command, "Heart is not beating"); } } catch (final InterruptedException e) { - return new Answer(command, false, "CheckOnHostCommand: can't get status of host: InterruptedException"); + return new CheckOnHostAnswer(command, "CheckOnHostCommand: can't get status of host: InterruptedException"); } catch (final ExecutionException e) { - return new Answer(command, false, "CheckOnHostCommand: can't get status of host: ExecutionException"); + return new CheckOnHostAnswer(command, "CheckOnHostCommand: can't get status of host: ExecutionException"); } } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java index d3f537dc9173..b132f6d98ce9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java @@ -49,8 +49,8 @@ public Answer execute(final CheckVMActivityOnStoragePoolCommand command, final L KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid()); if (primaryPool.isPoolSupportHA()) { - final HAStoragePool nfspool = monitor.getStoragePool(pool.getUuid()); - final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(nfspool, command.getHost(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds()); + final HAStoragePool haPool = monitor.getStoragePool(pool.getUuid()); + final KVMHAVMActivityChecker ha = new KVMHAVMActivityChecker(haPool, command.getHost(), command.getVolumeList(), libvirtComputingResource.getVmActivityCheckPath(), command.getSuspectTimeInSeconds()); final Future future = executors.submit(ha); try { final Boolean result = future.get(); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java index 1536984f2e85..b1f64c8c6dbb 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtScaleVmCommandWrapper.java @@ -49,10 +49,11 @@ public Answer execute(ScaleVmCommand command, LibvirtComputingResource libvirtCo conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName); Domain dm = conn.domainLookupByName(vmName); - logger.debug(String.format("Scaling %s.", scalingDetails)); + logger.debug("Scaling {}.", scalingDetails); scaleMemory(dm, newMemory, vmDefinition); scaleVcpus(dm, newVcpus, vmDefinition); updateCpuShares(dm, newCpuShares); + libvirtComputingResource.updateCpuQuotaAndPeriod(dm, vmSpec, command.getLimitCpuUseChange()); return new ScaleVmAnswer(command, true, String.format("Successfully scaled %s.", scalingDetails)); } catch (LibvirtException | CloudRuntimeException e) { @@ -74,7 +75,7 @@ protected void updateCpuShares(Domain dm, int newCpuShares) throws LibvirtExcept if (oldCpuShares < newCpuShares) { LibvirtComputingResource.setCpuShares(dm, newCpuShares); - logger.info(String.format("Successfully increased cpu_shares of VM [%s] from [%s] to [%s].", dm.getName(), oldCpuShares, newCpuShares)); + logger.info("Successfully increased cpu_shares of VM [{}] from [{}] to [{}].", dm.getName(), oldCpuShares, newCpuShares); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java index f5bfd898a4f4..8cf6de68f955 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/IscsiAdmStoragePool.java @@ -208,12 +208,12 @@ public String getStorageNodeId() { } @Override - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { + public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) { return null; } @Override - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { + public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { return null; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java index 8dd2116e1235..3e35ed9476b1 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePool.java @@ -33,35 +33,33 @@ public interface KVMStoragePool { - public static final long HeartBeatUpdateTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT); - public static final long HeartBeatUpdateFreq = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY); - public static final long HeartBeatUpdateMaxTries = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES); - public static final long HeartBeatUpdateRetrySleep = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_RETRY_SLEEP); - public static final long HeartBeatCheckerTimeout = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_CHECKER_TIMEOUT); + long HeartBeatUpdateTimeoutInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT); + long HeartBeatUpdateFreqInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY); + long HeartBeatCheckerTimeoutInMs = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_CHECKER_TIMEOUT); - public default KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) { + default KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, Long usableSize, byte[] passphrase) { return createPhysicalDisk(volumeUuid, format, provisioningType, size, passphrase); } - public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase); + KVMPhysicalDisk createPhysicalDisk(String volumeUuid, PhysicalDiskFormat format, Storage.ProvisioningType provisioningType, long size, byte[] passphrase); - public KVMPhysicalDisk createPhysicalDisk(String volumeUuid, Storage.ProvisioningType provisioningType, long size, byte[] passphrase); + KVMPhysicalDisk createPhysicalDisk(String volumeUuid, Storage.ProvisioningType provisioningType, long size, byte[] passphrase); - public boolean connectPhysicalDisk(String volumeUuid, Map details); + boolean connectPhysicalDisk(String volumeUuid, Map details); - public KVMPhysicalDisk getPhysicalDisk(String volumeUuid); + KVMPhysicalDisk getPhysicalDisk(String volumeUuid); - public boolean disconnectPhysicalDisk(String volumeUuid); + boolean disconnectPhysicalDisk(String volumeUuid); - public boolean deletePhysicalDisk(String volumeUuid, Storage.ImageFormat format); + boolean deletePhysicalDisk(String volumeUuid, Storage.ImageFormat format); - public List listPhysicalDisks(); + List listPhysicalDisks(); - public String getUuid(); + String getUuid(); - public long getCapacity(); + long getCapacity(); - public long getUsed(); + long getUsed(); default Long getCapacityIops() { return null; @@ -71,51 +69,51 @@ default Long getUsedIops() { return null; } - public long getAvailable(); + long getAvailable(); - public boolean refresh(); + boolean refresh(); - public boolean isExternalSnapshot(); + boolean isExternalSnapshot(); - public String getLocalPath(); + String getLocalPath(); - public String getSourceHost(); + String getSourceHost(); - public String getSourceDir(); + String getSourceDir(); - public int getSourcePort(); + int getSourcePort(); - public String getAuthUserName(); + String getAuthUserName(); - public String getAuthSecret(); + String getAuthSecret(); - public StoragePoolType getType(); + StoragePoolType getType(); - public boolean delete(); + boolean delete(); PhysicalDiskFormat getDefaultFormat(); - public boolean createFolder(String path); + boolean createFolder(String path); - public boolean supportsConfigDriveIso(); + boolean supportsConfigDriveIso(); - public Map getDetails(); + Map getDetails(); default String getLocalPathFor(String relativePath) { return String.format("%s%s%s", getLocalPath(), File.separator, relativePath); } - public boolean isPoolSupportHA(); + boolean isPoolSupportHA(); - public String getHearthBeatPath(); + String getHearthBeatPath(); - public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation); + String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation); - public String getStorageNodeId(); + String getStorageNodeId(); - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host); + Boolean hasHeartBeat(HAStoragePool pool, HostTO host); - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration); + Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration); default LibvirtVMDef.DiskDef.BlockIOSize getSupportedLogicalBlockSize() { return null; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java index 6e03b84d20cf..f95ebff5326f 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStorageProcessor.java @@ -2047,7 +2047,7 @@ private void waitForBackup(String vmName) throws CloudRuntimeException { try { Thread.sleep(10000); } catch (InterruptedException e) { - throw new CloudRuntimeException(e); + logger.trace("Thread that was tracking the progress for backup of VM [{}] was interrupted. Ignoring.", vmName); } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java index 45c22d3ac754..910f0eb15e0b 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java @@ -345,16 +345,14 @@ public String getHearthBeatPath() { public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) { - Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeout, logger); + Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeoutInMs, logger); cmd.add("-i", primaryStoragePool.getPoolIp()); cmd.add("-p", primaryStoragePool.getPoolMountSourcePath()); cmd.add("-m", primaryStoragePool.getMountDestPath()); if (hostValidation) { cmd.add("-h", hostPrivateIp); - } - - if (!hostValidation) { + } else { cmd.add("-c"); } @@ -372,53 +370,53 @@ public String getStorageNodeId() { } @Override - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { - boolean validResult = false; + public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) { String hostIp = host.getPrivateNetwork().getIp(); - Script cmd = new Script(getHearthBeatPath(), HeartBeatCheckerTimeout, logger); + Script cmd = new Script(getHearthBeatPath(), HeartBeatCheckerTimeoutInMs, logger); cmd.add("-i", pool.getPoolIp()); cmd.add("-p", pool.getPoolMountSourcePath()); cmd.add("-m", pool.getMountDestPath()); cmd.add("-h", hostIp); cmd.add("-r"); - cmd.add("-t", String.valueOf(HeartBeatUpdateFreq / 1000)); + cmd.add("-t", String.valueOf(HeartBeatUpdateFreqInMs / 1000)); OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); String result = cmd.execute(parser); String parsedLine = parser.getLine(); - logger.debug(String.format("Checking heart beat with KVMHAChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine, - pool.getPoolIp())); + logger.debug("Checking heart beat for host IP {} with KVMHAChecker [{command=\"{}\", result: \"{}\", log: \"{}\", pool: \"{}\"}].", hostIp, cmd.toString(), result, parsedLine, pool.getPoolIp()); if (result == null && parsedLine.contains("DEAD")) { - logger.warn(String.format("Checking heart beat with KVMHAChecker command [%s] returned [%s]. [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(), - result, parsedLine, hostIp)); + logger.warn("Checking heart beat for host IP {} with KVMHAChecker command [{}] returned [{}]. It may cause a shutdown of the host.", hostIp, cmd.toString(), parsedLine); + return false; } else { - validResult = true; + logger.debug("Checking heart beat for host IP {} with KVMHAChecker command [{}] succeeded.", hostIp, cmd.toString()); + return true; } - return validResult; } @Override - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { + public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { + String hostIp = host.getPrivateNetwork().getIp(); Script cmd = new Script(vmActivityCheckPath, activityScriptTimeout.getStandardSeconds(), logger); cmd.add("-i", pool.getPoolIp()); cmd.add("-p", pool.getPoolMountSourcePath()); cmd.add("-m", pool.getMountDestPath()); - cmd.add("-h", host.getPrivateNetwork().getIp()); + cmd.add("-h", hostIp); cmd.add("-u", volumeUUIDListString); - cmd.add("-t", String.valueOf(String.valueOf(System.currentTimeMillis() / 1000))); + cmd.add("-t", String.valueOf(System.currentTimeMillis() / 1000)); cmd.add("-d", String.valueOf(duration)); OutputInterpreter.OneLineParser parser = new OutputInterpreter.OneLineParser(); String result = cmd.execute(parser); String parsedLine = parser.getLine(); - logger.debug(String.format("Checking heart beat with KVMHAVMActivityChecker [{command=\"%s\", result: \"%s\", log: \"%s\", pool: \"%s\"}].", cmd.toString(), result, parsedLine, pool.getPoolIp())); + logger.debug("Checking VM activity for host IP {} with KVMHAVMActivityChecker [{command=\"{}\", result: \"{}\", log: \"{}\", pool: \"{}\"}].", hostIp, cmd.toString(), result, parsedLine, pool.getPoolIp()); if (result == null && parsedLine.contains("DEAD")) { - logger.warn(String.format("Checking heart beat with KVMHAVMActivityChecker command [%s] returned [%s]. It is [%s]. It may cause a shutdown of host IP [%s].", cmd.toString(), result, parsedLine, host.getPrivateNetwork().getIp())); + logger.warn("Checking VM activity for host IP {} with KVMHAVMActivityChecker command [{}] returned [{}]. It may cause a shutdown of the host.", hostIp, cmd.toString(), parsedLine); return false; } else { + logger.debug("Checking VM activity for host IP {} with KVMHAVMActivityChecker command [{}] succeeded.", hostIp, cmd.toString()); return true; } } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java index 229481b1f795..df9986c99194 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/MultipathSCSIPool.java @@ -225,13 +225,13 @@ public String getStorageNodeId() { } @Override - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { + public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) { return null; } @Override - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, - String volumeUUIDListString, String vmActivityCheckPath, long duration) { + public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, + String volumeUUIDListString, String vmActivityCheckPath, long duration) { return null; } diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java index e8243c3f7cfe..fc512ff94a94 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStoragePool.java @@ -236,12 +236,12 @@ public String getStorageNodeId() { } @Override - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { + public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) { return null; } @Override - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { + public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { return null; } } diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAConfig.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAConfig.java index 59ea720328f5..3fbb5340fcce 100644 --- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAConfig.java +++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHAConfig.java @@ -19,38 +19,37 @@ import org.apache.cloudstack.framework.config.ConfigKey; -public class KVMHAConfig { +public interface KVMHAConfig { - public static final ConfigKey KvmHAHealthCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.health.check.timeout", "10", + ConfigKey KvmHAHealthCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.health.check.timeout", "10", "The maximum length of time, in seconds, expected for an health check to complete.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHAActivityCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.timeout", "60", + ConfigKey KvmHAActivityCheckTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.timeout", "60", "The maximum length of time, in seconds, expected for an activity check to complete.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHAActivityCheckInterval = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.interval", "60", + ConfigKey KvmHAActivityCheckInterval = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.interval", "60", "The interval, in seconds, between activity checks.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHAActivityCheckMaxAttempts = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.max.attempts", "10", + ConfigKey KvmHAActivityCheckMaxAttempts = new ConfigKey<>("Advanced", Long.class, "kvm.ha.activity.check.max.attempts", "10", "The maximum number of activity check attempts to perform before deciding to recover or degrade a resource.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHAActivityCheckFailureThreshold = new ConfigKey<>("Advanced", Double.class, "kvm.ha.activity.check.failure.ratio", "0.7", + ConfigKey KvmHAActivityCheckFailureThreshold = new ConfigKey<>("Advanced", Double.class, "kvm.ha.activity.check.failure.ratio", "0.7", "The activity check failure threshold ratio. This is used with the activity check maximum attempts for deciding to recover or degrade a resource. For most environments, please keep this value above 0.5.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHADegradedMaxPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.degraded.max.period", "300", + ConfigKey KvmHADegradedMaxPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.degraded.max.period", "300", "The maximum length of time, in seconds, a resource can be in degraded state where only health checks are performed.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHARecoverTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.timeout", "60", + ConfigKey KvmHARecoverTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.timeout", "60", "The maximum length of time, in seconds, expected for a recovery operation to complete.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHARecoverWaitPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.wait.period", "600", + ConfigKey KvmHARecoverWaitPeriod = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.wait.period", "600", "The maximum length of time, in seconds, to wait for a resource to recover.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHARecoverAttemptThreshold = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.failure.threshold", "1", + ConfigKey KvmHARecoverAttemptThreshold = new ConfigKey<>("Advanced", Long.class, "kvm.ha.recover.failure.threshold", "1", "The maximum recovery attempts to be made for a resource, after which the resource is fenced. The recovery counter resets when a health check passes for a resource.", true, ConfigKey.Scope.Cluster); - public static final ConfigKey KvmHAFenceTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.fence.timeout", "60", + ConfigKey KvmHAFenceTimeout = new ConfigKey<>("Advanced", Long.class, "kvm.ha.fence.timeout", "60", "The maximum length of time, in seconds, expected for a fence operation to complete.", true, ConfigKey.Scope.Cluster); - } 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 b937be5265b7..f0b5cfc337de 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 @@ -68,17 +68,18 @@ public boolean hasActivity(final Host r, final DateTime suspectTime) throws HACh @Override public boolean recover(Host r) throws HARecoveryException { + logger.debug("Recover the host {}", r); try { - if (outOfBandManagementService.isOutOfBandManagementEnabled(r)){ + if (outOfBandManagementService.isOutOfBandManagementEnabled(r)) { final OutOfBandManagementResponse resp = outOfBandManagementService.executePowerOperation(r, PowerOperation.RESET, null); return resp.getSuccess(); } else { logger.warn("OOBM recover operation failed for the host {}", r); return false; } - } catch (Exception e){ + } catch (Exception e) { logger.warn("OOBM service is not configured or enabled for this host {} error is {}", r, e.getMessage()); - throw new HARecoveryException(String.format(" OOBM service is not configured or enabled for this host %s", r), e); + throw new HARecoveryException(String.format("OOBM service is not configured or enabled for this host %s", r), e); } } 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 31f87d7e0442..af7441c4fd29 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 @@ -19,6 +19,7 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckOnHostAnswer; import com.cloud.agent.api.CheckOnHostCommand; import com.cloud.agent.api.CheckVMActivityOnStoragePoolCommand; import com.cloud.dc.dao.ClusterDao; @@ -61,7 +62,7 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck @Inject private AgentManager agentMgr; @Inject - private PrimaryDataStoreDao storagePool; + private PrimaryDataStoreDao storagePoolDao; @Inject private StorageManager storageManager; @Inject @@ -70,11 +71,11 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck @Override public boolean isActive(Host r, DateTime suspectTime) throws HACheckerException { try { - return isVMActivityOnHost(r, suspectTime); + return hasVMActivityOnHost(r, suspectTime); } catch (HACheckerException e) { - //Re-throwing the exception to avoid poluting the 'HACheckerException' already thrown + //Re-throwing the exception to avoid polluting the 'HACheckerException' already thrown throw e; - } catch (Exception e){ + } catch (Exception e) { String message = String.format("Operation timed out, probably the %s is not reachable.", r.toString()); logger.warn(message, e); throw new HACheckerException(message, e); @@ -83,82 +84,115 @@ public boolean isActive(Host r, DateTime suspectTime) throws HACheckerException @Override public boolean isHealthy(Host r) { - return isAgentActive(r); + return isHostAgentUp(r); } - private boolean isAgentActive(Host agent) { - if (agent.getHypervisorType() != Hypervisor.HypervisorType.KVM && agent.getHypervisorType() != Hypervisor.HypervisorType.LXC) { - throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", agent.getHypervisorType())); + private boolean isHostAgentUp(Host host) { + if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) { + throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", host.getHypervisorType())); + } + + Status hostStatus = getHostAgentStatus(host); + + logger.debug("{} has the status [{}].", host.toString(), hostStatus); + return hostStatus == Status.Up; + } + + public Status getHostAgentStatus(Host host) { + if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) { + return null; + } + + Status hostStatusFromItself = checkHostStatusWithSameHost(host); + if (hostStatusFromItself == Status.Up) { + return Status.Up; } - Status hostStatus = Status.Unknown; - Status neighbourStatus = Status.Unknown; - final CheckOnHostCommand cmd = new CheckOnHostCommand(agent, HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value()); + + Status hostStatusFromNeighbour = checkHostStatusWithNeighbourHosts(host); + Status hostStatus = hostStatusFromItself; + if (hostStatusFromNeighbour == Status.Up && (hostStatusFromItself == Status.Disconnected || hostStatusFromItself == Status.Down)) { + hostStatus = Status.Disconnected; + } + if (hostStatusFromNeighbour == Status.Down && (hostStatusFromItself == Status.Disconnected || hostStatusFromItself == Status.Down)) { + hostStatus = Status.Down; + } + + logger.debug("HA: HOST is ineligible legacy state {} for host {}", hostStatus, host); + return hostStatus; + } + + private Status checkHostStatusWithSameHost(Host host) { + Status hostStatus; + boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value(); + final CheckOnHostCommand cmd = new CheckOnHostCommand(host, reportFailureIfOneStorageIsDown); try { - logger.debug(String.format("Checking %s status...", agent.toString())); - Answer answer = agentMgr.easySend(agent.getId(), cmd); + logger.debug("Checking {} status...", host.toString()); + Answer answer = agentMgr.easySend(host.getId(), cmd); if (answer != null) { - hostStatus = answer.getResult() ? Status.Down : Status.Up; - logger.debug(String.format("%s has the status [%s].", agent.toString(), hostStatus)); - - if ( hostStatus == Status.Up ){ - return true; + if (answer.getResult()) { + hostStatus = ((CheckOnHostAnswer)answer).isAlive() ? Status.Up : Status.Down; + } else { + logger.debug("{} is not active according to itself, details: {}.", host.toString(), answer.getDetails()); + hostStatus = Status.Down; } - } - else { - logger.debug(String.format("Setting %s to \"Disconnected\" status.", agent.toString())); + logger.debug("{} has the status [{}].", host.toString(), hostStatus); + } else { + logger.debug("Setting {} to \"Disconnected\" status.", host.toString()); hostStatus = Status.Disconnected; } } catch (Exception e) { - logger.warn(String.format("Failed to send command CheckOnHostCommand to %s.", agent.toString()), e); + logger.warn("Failed to send command CheckOnHostCommand to {}.", host.toString(), e); + hostStatus = Status.Disconnected; } - List neighbors = resourceManager.listHostsInClusterByStatus(agent.getClusterId(), Status.Up); + return hostStatus; + } + + private Status checkHostStatusWithNeighbourHosts(Host host) { + Status hostStatusFromNeighbour = Status.Unknown; + boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value(); + final CheckOnHostCommand cmd = new CheckOnHostCommand(host, reportFailureIfOneStorageIsDown); + List neighbors = resourceManager.listHostsInClusterByStatus(host.getClusterId(), Status.Up); for (HostVO neighbor : neighbors) { - if (neighbor.getId() == agent.getId() || (neighbor.getHypervisorType() != Hypervisor.HypervisorType.KVM && neighbor.getHypervisorType() != Hypervisor.HypervisorType.LXC)) { + if (neighbor.getId() == host.getId() + || (neighbor.getHypervisorType() != Hypervisor.HypervisorType.KVM && neighbor.getHypervisorType() != Hypervisor.HypervisorType.LXC)) { continue; } try { - logger.debug(String.format("Investigating %s via neighbouring %s.", agent.toString(), neighbor.toString())); - + logger.debug("Investigating {} via neighboring {}.", host.toString(), neighbor.toString()); Answer answer = agentMgr.easySend(neighbor.getId(), cmd); if (answer != null) { - neighbourStatus = answer.getResult() ? Status.Down : Status.Up; - - logger.debug(String.format("Neighbouring %s returned status [%s] for the investigated %s.", neighbor.toString(), neighbourStatus, agent.toString())); - - if (neighbourStatus == Status.Up) { - break; + if (answer.getResult()) { + hostStatusFromNeighbour = ((CheckOnHostAnswer)answer).isAlive() ? Status.Up : Status.Down; + logger.debug("Neighboring {} returned status [{}] for the investigated {}.", neighbor.toString(), hostStatusFromNeighbour, host.toString()); + if (hostStatusFromNeighbour == Status.Up) { + return hostStatusFromNeighbour; + } + } else { + logger.debug("{} is not active according to neighbor {}, details: {}.", host.toString(), neighbor.toString(), answer.getDetails()); } } else { - logger.debug(String.format("Neighbouring %s is Disconnected.", neighbor.toString())); + logger.debug("Neighboring {} is Disconnected.", neighbor.toString()); } } catch (Exception e) { - logger.warn(String.format("Failed to send command CheckOnHostCommand to %s.", neighbor.toString()), e); + logger.warn("Failed to send command CheckOnHostCommand to neighbor {}.", neighbor.toString(), e); } } - if (neighbourStatus == Status.Up && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) { - hostStatus = Status.Disconnected; - } - if (neighbourStatus == Status.Down && (hostStatus == Status.Disconnected || hostStatus == Status.Down)) { - hostStatus = Status.Down; - } - logger.debug(String.format("%s has the status [%s].", agent.toString(), hostStatus)); - - return hostStatus == Status.Up; + return hostStatusFromNeighbour; } - private boolean isVMActivityOnHost(Host agent, DateTime suspectTime) throws HACheckerException { - if (agent.getHypervisorType() != Hypervisor.HypervisorType.KVM && agent.getHypervisorType() != Hypervisor.HypervisorType.LXC) { - throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", agent.getHypervisorType())); + private boolean hasVMActivityOnHost(Host host, DateTime suspectTime) throws HACheckerException { + if (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) { + throw new IllegalStateException(String.format("Calling KVM investigator for non KVM Host of type [%s].", host.getHypervisorType())); } boolean activityStatus = true; - HashMap> poolVolMap = getVolumeUuidOnHost(agent); - for (StoragePool pool : poolVolMap.keySet()) { - activityStatus = verifyActivityOfStorageOnHost(poolVolMap, pool, agent, suspectTime, activityStatus); + HashMap> poolVolumeMap = getStoragePoolAndVolumeInfoOnHost(host); + for (StoragePool pool : poolVolumeMap.keySet()) { + activityStatus = verifyActivityOfStorageOnHost(poolVolumeMap, pool, host, suspectTime, activityStatus); if (!activityStatus) { - logger.warn("It seems that the storage pool [{}] does not have activity on {}.", pool, agent); + logger.warn("It seems that the storage pool [{}] does not have activity on {}.", pool, host); break; } } @@ -166,66 +200,64 @@ private boolean isVMActivityOnHost(Host agent, DateTime suspectTime) throws HACh return activityStatus; } - protected boolean verifyActivityOfStorageOnHost(HashMap> poolVolMap, StoragePool pool, Host agent, DateTime suspectTime, boolean activityStatus) throws HACheckerException, IllegalStateException { + protected boolean verifyActivityOfStorageOnHost(HashMap> poolVolMap, StoragePool pool, Host host, DateTime suspectTime, boolean activityStatus) throws HACheckerException, IllegalStateException { List volume_list = poolVolMap.get(pool); - final CheckVMActivityOnStoragePoolCommand cmd = new CheckVMActivityOnStoragePoolCommand(agent, pool, volume_list, suspectTime); + final CheckVMActivityOnStoragePoolCommand cmd = new CheckVMActivityOnStoragePoolCommand(host, pool, volume_list, suspectTime); - logger.debug("Checking VM activity for {} on storage pool [{}].", agent.toString(), pool); + logger.debug("Checking VM activity for {} on storage pool [{}].", host.toString(), pool); try { - Answer answer = storageManager.sendToPool(pool, getNeighbors(agent), cmd); - + Answer answer = storageManager.sendToPool(pool, getNeighbors(host), cmd); if (answer != null) { activityStatus = !answer.getResult(); - logger.debug("{} {} activity on storage pool [{}]", agent.toString(), activityStatus ? "has" : "does not have", pool); + logger.debug("{} {} activity on storage pool [{}]", host.toString(), activityStatus ? "has" : "does not have", pool); } else { - String message = String.format("Did not get a valid response for VM activity check for %s on storage pool [%s].", agent.toString(), pool); + String message = String.format("Did not get a valid response for VM activity check for %s on storage pool [%s].", host.toString(), pool); logger.debug(message); throw new IllegalStateException(message); } - } catch (StorageUnavailableException e){ - String message = String.format("Storage [%s] is unavailable to do the check, probably the %s is not reachable.", pool, agent); + } catch (StorageUnavailableException e) { + String message = String.format("Storage [%s] is unavailable to do the check, probably the %s is not reachable.", pool, host); logger.warn(message, e); throw new HACheckerException(message, e); } return activityStatus; } - private HashMap> getVolumeUuidOnHost(Host agent) { - List vm_list = vmInstanceDao.listByHostId(agent.getId()); - List volume_list = new ArrayList(); - for (VirtualMachine vm : vm_list) { + private HashMap> getStoragePoolAndVolumeInfoOnHost(Host host) { + List vmListOnHost = vmInstanceDao.listByHostId(host.getId()); + List volumeListOnHost = new ArrayList<>(); + for (VirtualMachine vm : vmListOnHost) { logger.debug("Retrieving volumes of VM [{}]...", vm); - List vm_volume_list = volumeDao.findByInstance(vm.getId()); - volume_list.addAll(vm_volume_list); + List volumeListOfVM = volumeDao.findByInstance(vm.getId()); + volumeListOnHost.addAll(volumeListOfVM); } - HashMap> poolVolMap = new HashMap>(); - for (Volume vol : volume_list) { - StoragePool sp = storagePool.findById(vol.getPoolId()); - logger.debug("Retrieving storage pool [{}] of volume [{}]...", sp, vol); - if (!poolVolMap.containsKey(sp)) { - List list = new ArrayList(); - list.add(vol); + HashMap> poolVolumeMap = new HashMap<>(); + for (Volume volume : volumeListOnHost) { + StoragePool pool = storagePoolDao.findById(volume.getPoolId()); + logger.debug("Retrieving storage pool [{}] of volume [{}]...", pool, volume); + if (!poolVolumeMap.containsKey(pool)) { + List volList = new ArrayList<>(); + volList.add(volume); - poolVolMap.put(sp, list); + poolVolumeMap.put(pool, volList); } else { - poolVolMap.get(sp).add(vol); + poolVolumeMap.get(pool).add(volume); } } - return poolVolMap; + return poolVolumeMap; } - public long[] getNeighbors(Host agent) { - List neighbors = new ArrayList(); - List cluster_hosts = resourceManager.listHostsInClusterByStatus(agent.getClusterId(), Status.Up); - logger.debug("Retrieving all \"Up\" hosts from cluster [{}]...", clusterDao.findById(agent.getClusterId())); - for (HostVO host : cluster_hosts) { - if (host.getId() == agent.getId() || (host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC)) { + public long[] getNeighbors(Host host) { + List neighbors = new ArrayList<>(); + List clusterHosts = resourceManager.listHostsInClusterByStatus(host.getClusterId(), Status.Up); + logger.debug("Retrieving all \"Up\" hosts from cluster [{}]...", clusterDao.findById(host.getClusterId())); + for (HostVO clusterHost : clusterHosts) { + if (clusterHost.getId() == host.getId() || (clusterHost.getHypervisorType() != Hypervisor.HypervisorType.KVM && clusterHost.getHypervisorType() != Hypervisor.HypervisorType.LXC)) { continue; } - neighbors.add(host.getId()); + neighbors.add(clusterHost.getId()); } return ArrayUtils.toPrimitive(neighbors.toArray(new Long[neighbors.size()])); } - } diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index b96295240076..b3bdafb73751 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -3133,7 +3133,7 @@ public void testCheckOnHostCommand() { assertNotNull(wrapper); final Answer answer = wrapper.execute(command, libvirtComputingResourceMock); - assertTrue(answer.getResult()); + assertFalse(answer.getResult()); verify(libvirtComputingResourceMock, times(1)).getMonitor(); } @@ -5642,35 +5642,45 @@ public void testAddExtraConfigComponentNotEmptyExtraConfig() { Mockito.verify(vmDef, times(1)).addComp(any()); } - public void validateGetCurrentMemAccordingToMemBallooningWithoutMemBalooning(){ + @Test + public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryWithoutMemBallooning(){ VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); - Mockito.when(vmTo.getType()).thenReturn(Type.User); LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); libvirtComputingResource.noMemBalloon = true; - long maxMemory = 2048; + long requestedMemory = 1024 * 1024; + long minMemory = 512 * 1024; - long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory); - Assert.assertEquals(maxMemory, currentMemory); - Mockito.verify(vmTo, Mockito.times(0)).getMinRam(); + long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory); + Assert.assertEquals(requestedMemory, currentMemory); } @Test - public void validateGetCurrentMemAccordingToMemBallooningWithtMemBalooning(){ + public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryWithMemoryBallooning(){ LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); libvirtComputingResource.noMemBalloon = false; - long maxMemory = 2048; - long minMemory = ByteScaleUtils.mebibytesToBytes(64); - VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); Mockito.when(vmTo.getType()).thenReturn(Type.User); - Mockito.when(vmTo.getMinRam()).thenReturn(minMemory); + long requestedMemory = 1024 * 1024; + long minMemory = 512 * 1024; - long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, maxMemory); - Assert.assertEquals(ByteScaleUtils.bytesToKibibytes(minMemory), currentMemory); - Mockito.verify(vmTo).getMinRam(); + long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory); + Assert.assertEquals(minMemory, currentMemory); } + @Test + public void getCurrentMemAccordingToMemBallooningTestValidateCurrentMemoryForSystemVms() { + LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); + libvirtComputingResource.noMemBalloon = false; + + VirtualMachineTO vmTo = Mockito.mock(VirtualMachineTO.class); + Mockito.when(vmTo.getType()).thenReturn(Type.SecondaryStorageVm); + long requestedMemory = 1024 * 1024; + long minMemory = 512 * 1024; + + long currentMemory = libvirtComputingResource.getCurrentMemAccordingToMemBallooning(vmTo, requestedMemory, minMemory); + Assert.assertEquals(requestedMemory, currentMemory); + } @Test public void validateCreateGuestResourceDefWithVcpuMaxLimit(){ LibvirtComputingResource libvirtComputingResource = new LibvirtComputingResource(); @@ -7234,4 +7244,82 @@ public void getInterfaceTestInvalidMacAddressThrowCloudRuntimeException() { libvirtComputingResourceSpy.getInterface(connMock, vmName, invalidMacAddress); } + + @Test + public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreNotUpdatedWhenLibvirtVersionIsLessThanTheMinimum() throws LibvirtException { + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 8999; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, null, false); + Mockito.verify(domainMock, Mockito.never()).setSchedulerParameters(Mockito.any()); + } + + @Test + public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreNotUpdatedWhenThereIsNoCapCapChangeAndNoCpuLimitationIsApplied() throws LibvirtException { + Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false); + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, false); + Mockito.verify(domainMock, Mockito.never()).setSchedulerParameters(Mockito.any()); + } + + @Test + public void updateCpuQuotaAndPeriodTestAssertQuotaIsRemovedWhenThereIsCpuCapChangeAndNoCpuLimitationIsApplied() throws LibvirtException { + Mockito.when(vmTO.isLimitCpuUse()).thenReturn(false); + Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM"); + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, true); + Mockito.verify(domainMock, Mockito.times(1)).setSchedulerParameters(Mockito.any()); + } + + @Test + public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreUpdatedWhenThereIsNotCpuCapChangeAndCpuLimitationIsApplied() throws LibvirtException { + Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true); + double cpuQuotaPercentage = 0.03; + Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(cpuQuotaPercentage); + Mockito.doReturn(new Pair<>(1000, 300L)).when(libvirtComputingResourceSpy).getPeriodAndQuota(cpuQuotaPercentage); + Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM"); + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, false); + Mockito.verify(domainMock, Mockito.times(2)).setSchedulerParameters(Mockito.any()); + } + + @Test + public void updateCpuQuotaAndPeriodTestAssertPeriodAndQuotaAreUpdatedWhenThereIsCpuCapChangeAndCpuLimitationIsApplied() throws LibvirtException { + Mockito.when(vmTO.isLimitCpuUse()).thenReturn(true); + double cpuQuotaPercentage = 0.03; + Mockito.when(vmTO.getCpuQuotaPercentage()).thenReturn(cpuQuotaPercentage); + Mockito.doReturn(new Pair<>(1000, 300L)).when(libvirtComputingResourceSpy).getPeriodAndQuota(cpuQuotaPercentage); + Mockito.when(domainMock.getName()).thenReturn("i-2-10-VM"); + libvirtComputingResourceSpy.hypervisorLibvirtVersion = 9000; + libvirtComputingResourceSpy.updateCpuQuotaAndPeriod(domainMock, vmTO, true); + Mockito.verify(domainMock, Mockito.times(2)).setSchedulerParameters(Mockito.any()); + } + + @Test + public void getPeriodAndQuotaTestAssertQuotaIsEqualToPeriodMultipliedByQuotaPercentage() { + double cpuQuotaPercentage = 0.3; + int expectedPeriod = CpuTuneDef.DEFAULT_PERIOD; + long expectedQuota = (long) (expectedPeriod * cpuQuotaPercentage); + Pair expectedResult = new Pair<>(expectedPeriod, expectedQuota); + Pair result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage); + Assert.assertEquals(expectedResult, result); + } + + @Test + public void getPeriodAndQuotaTestQuotaIsEqualToMinimumWhenRequired() { + double cpuQuotaPercentage = 0.03; + long expectedQuota = CpuTuneDef.MIN_QUOTA; + int expectedPeriod = (int) ((double) expectedQuota / cpuQuotaPercentage); + Pair expectedResult = new Pair<>(expectedPeriod, expectedQuota); + Pair result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage); + Assert.assertEquals(expectedResult, result); + } + + @Test + public void getPeriodAndQuotaTestPeriodIsEqualToMaximumWhenRequired() { + double cpuQuotaPercentage = 0.0003; + long expectedQuota = CpuTuneDef.MIN_QUOTA; + int expectedPeriod = CpuTuneDef.MAX_PERIOD; + Pair expectedResult = new Pair<>(expectedPeriod, expectedQuota); + Pair result = libvirtComputingResourceSpy.getPeriodAndQuota(cpuQuotaPercentage); + Assert.assertEquals(expectedResult, result); + } } diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java index 5a0274257764..140302590ba2 100644 --- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java +++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImgTest.java @@ -57,7 +57,7 @@ public static void setUp() { Connect conn = new Connect("qemu:///system", false); conn.getVersion(); libVirtAvailable = true; - } catch (LibvirtException ignored) {} + } catch (LibvirtException | UnsatisfiedLinkError | ExceptionInInitializerError ignored) {} Assume.assumeTrue("libvirt not available", libVirtAvailable); } diff --git a/plugins/hypervisors/simulator/src/main/java/com/cloud/ha/SimulatorInvestigator.java b/plugins/hypervisors/simulator/src/main/java/com/cloud/ha/SimulatorInvestigator.java index 7114a8411578..95a6a1291cf1 100644 --- a/plugins/hypervisors/simulator/src/main/java/com/cloud/ha/SimulatorInvestigator.java +++ b/plugins/hypervisors/simulator/src/main/java/com/cloud/ha/SimulatorInvestigator.java @@ -54,13 +54,13 @@ protected SimulatorInvestigator() { } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { if (agent.getHypervisorType() != HypervisorType.Simulator) { return null; } if (haManager.isHAEligible(agent)) { - return haManager.getHostStatus(agent); + return haManager.getHostStatusFromHAConfig(agent); } CheckOnHostCommand cmd = new CheckOnHostCommand(agent); diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/ha/VmwareInvestigator.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/ha/VmwareInvestigator.java index 5bfc18968430..3d4fab0d229e 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/ha/VmwareInvestigator.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/ha/VmwareInvestigator.java @@ -28,7 +28,7 @@ protected VmwareInvestigator() { } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { if (agent.getHypervisorType() == HypervisorType.VMware) return Status.Disconnected; diff --git a/plugins/integrations/kubernetes-service/pom.xml b/plugins/integrations/kubernetes-service/pom.xml index 6b5cec95aab9..f638dbde7f3b 100644 --- a/plugins/integrations/kubernetes-service/pom.xml +++ b/plugins/integrations/kubernetes-service/pom.xml @@ -124,16 +124,6 @@ ${cs.hamcrest.version} test - - org.bouncycastle - bcprov-jdk15on - ${cs.bcprov.version} - - - org.bouncycastle - bctls-jdk15on - ${cs.bcprov.version} - joda-time joda-time diff --git a/plugins/pom.xml b/plugins/pom.xml index e4904ccdf40b..44220481a6fe 100755 --- a/plugins/pom.xml +++ b/plugins/pom.xml @@ -84,8 +84,6 @@ ha-planners/skip-heurestics - host-allocators/random - hypervisors/baremetal hypervisors/external hypervisors/hyperv @@ -204,13 +202,13 @@ io.minio minio - 8.5.2 + ${cs.minio.version} compile io.minio minio-admin - 8.5.2 + ${cs.minio.version} compile diff --git a/plugins/storage/object/minio/pom.xml b/plugins/storage/object/minio/pom.xml index d69c2a7498fa..64d62cf8d347 100644 --- a/plugins/storage/object/minio/pom.xml +++ b/plugins/storage/object/minio/pom.xml @@ -46,12 +46,24 @@ io.minio minio - 8.5.2 + ${cs.minio.version} io.minio minio-admin - 8.5.2 + ${cs.minio.version} + + + + com.squareup.okhttp3 + okhttp + 5.1.0 + + + com.squareup.okhttp3 + logging-interceptor + 5.1.0 diff --git a/plugins/storage/object/simulator/pom.xml b/plugins/storage/object/simulator/pom.xml index dc4ab0146067..bd6c6ef99059 100644 --- a/plugins/storage/object/simulator/pom.xml +++ b/plugins/storage/object/simulator/pom.xml @@ -46,12 +46,12 @@ io.minio minio - 8.5.2 + ${cs.minio.version} io.minio minio-admin - 8.5.2 + ${cs.minio.version} diff --git a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java index 771f79887e0f..c8efc08c2892 100644 --- a/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java +++ b/plugins/storage/volume/adaptive/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/AdaptiveDataStoreLifeCycleImpl.java @@ -217,13 +217,14 @@ public DataStore initialize(Map dsInfos) { // validate the provided details are correct/valid for the provider api.validate(); - // if we have user-provided capacity bytes, validate they do not exceed the manaaged storage capacity bytes + // User-provided capacityBytes always wins; validate against storage stats only when + // the provider could actually report them. If the provider cannot (empty pod with no + // footprint, no quota set, transient probe failure), fall through and use what the + // user supplied rather than failing the whole registration. ProviderVolumeStorageStats stats = api.getManagedStorageStats(); - if (capacityBytes != null && capacityBytes != 0 && stats != null) { - if (stats.getCapacityInBytes() > 0) { - if (stats.getCapacityInBytes() < capacityBytes) { - throw new InvalidParameterValueException("Capacity bytes provided exceeds the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes()); - } + if (capacityBytes != null && capacityBytes > 0) { + if (stats != null && stats.getCapacityInBytes() > 0 && stats.getCapacityInBytes() < capacityBytes) { + throw new InvalidParameterValueException("Provided capacity bytes exceed the capacity of the storage endpoint: provided by user: " + capacityBytes + ", storage capacity from storage provider: " + stats.getCapacityInBytes()); } parameters.setCapacityBytes(capacityBytes); } diff --git a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java index 41125f3e1135..01207c6d224d 100644 --- a/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java +++ b/plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java @@ -23,7 +23,8 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.text.SimpleDateFormat; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -88,6 +89,9 @@ public class FlashArrayAdapter implements ProviderAdapter { static final ObjectMapper mapper = new ObjectMapper(); public String pod = null; public String hostgroup = null; + private static final DateTimeFormatter DELETION_TIMESTAMP_FORMAT = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC); + private String username; private String password; private String accessToken; @@ -200,28 +204,63 @@ public void detach(ProviderAdapterContext context, ProviderAdapterDataObject dat @Override public void delete(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) { - // first make sure we are disconnected - removeVlunsAll(context, pod, dataObject.getExternalName()); String fullName = normalizeName(pod, dataObject.getExternalName()); - FlashArrayVolume volume = new FlashArrayVolume(); + // Snapshots live under /volume-snapshots and already use the array's + // reserved form ., which legitimately contains ".". + // The stricter [A-Za-z0-9_-] naming rule applies to regular volume + // names and free-form rename targets, not to these reserved snapshot + // names. Since FlashArray snapshot names are system-defined rather + // than arbitrary rename targets, we skip the usual timestamped rename + // and only mark snapshots destroyed; the array's own ".N" suffix + // already disambiguates them in the recycle bin. + if (dataObject.getType() == ProviderAdapterDataObject.Type.SNAPSHOT) { + try { + FlashArrayVolume destroy = new FlashArrayVolume(); + destroy.setDestroyed(true); + PATCH("/volume-snapshots?names=" + fullName, destroy, new TypeReference>() { + }); + } catch (CloudRuntimeException e) { + String msg = e.getMessage(); + if (msg != null && (msg.contains("No such volume or snapshot") + || msg.contains("Volume does not exist"))) { + return; + } + throw e; + } + return; + } - // rename as we delete so it doesn't conflict if the template or volume is ever recreated - // pure keeps the volume(s) around in a Destroyed bucket for a period of time post delete - String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date()); - volume.setExternalName(fullName + "-" + timestamp); + // first make sure we are disconnected + removeVlunsAll(context, pod, dataObject.getExternalName()); + + // Rename then destroy: FlashArray keeps destroyed volumes in a recycle + // bin (default 24h) from which they can be recovered. Renaming with a + // deletion timestamp gives operators a forensic trail when browsing the + // array - they can see when each destroyed copy was deleted on the + // CloudStack side. FlashArray rejects a single PATCH that combines + // {name, destroyed}, so the rename and the destroy must be issued as + // two separate requests each carrying only its own field. + // Use UTC so the rename suffix is stable regardless of the management + // server's local timezone or DST changes - operators correlating the + // CloudStack delete event with the array's audit log get a consistent + // wall-clock value. + String timestamp = DELETION_TIMESTAMP_FORMAT.format(java.time.Instant.now()); + String renamedName = fullName + "-" + timestamp; try { - PATCH("/volumes?names=" + fullName, volume, new TypeReference>() { + FlashArrayVolume rename = new FlashArrayVolume(); + rename.setExternalName(renamedName); + PATCH("/volumes?names=" + fullName, rename, new TypeReference>() { }); - // now delete it with new name - volume.setDestroyed(true); - - PATCH("/volumes?names=" + fullName + "-" + timestamp, volume, new TypeReference>() { + FlashArrayVolume destroy = new FlashArrayVolume(); + destroy.setDestroyed(true); + PATCH("/volumes?names=" + renamedName, destroy, new TypeReference>() { }); } catch (CloudRuntimeException e) { - if (e.toString().contains("Volume does not exist")) { + String msg = e.getMessage(); + if (msg != null && msg.contains("Volume does not exist")) { return; } else { throw e; diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 2b11c83c8021..32858108d6ac 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -508,12 +508,33 @@ private boolean deRefOrDeleteResource(DevelopersApi api, String rscName, String // if there is only one template-for property left for templates, the template isn't needed anymore // or if it isn't a template anyway, it will not have this Aux property // _cs-template-for- properties work like a ref-count. - if (rd.getProps().keySet().stream() + long remainingTemplateRefs = rd.getProps().keySet().stream() .filter(key -> key.startsWith("Aux/" + LinstorUtil.CS_TEMPLATE_FOR_PREFIX)) - .count() == expectedProps) { + .count(); + if (remainingTemplateRefs == expectedProps) { + // Surface the legacy case where a resource has zero `_cs-template-for-` aux + // properties even though we never decremented one — that's a template predating + // the ref-count convention, or a stale orphan. Logging before deletion lets + // operators audit how many such orphans existed at upgrade time. + if (expectedProps == 0) { + logger.info("Linstor: deleting resource {} which has no _cs-template-for- aux properties " + + "(legacy template predating the ref-count convention, or a stale orphan). " + + "Resource group context: {}", rd.getName(), rscGrpName); + } ApiCallRcList answers = api.resourceDefinitionDelete(rd.getName()); checkLinstorAnswersThrow(answers); deleted = true; + + // LINSTOR can return success here while the resource lingers in DELETING state + // on the controller (down peer, lost quorum, etc.). Confirm it's actually gone + // — if not, log a WARN so operators can clear it manually. Don't throw: the + // CloudStack-side accounting has already moved on. + if (!LinstorUtil.waitForResourceDefinitionDeleted(api, rd.getName(), + LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS)) { + logger.warn("Linstor: resource {} still present {}ms after delete returned success — " + + "may be stuck in DELETING. Check the LINSTOR controller (linstor resource list).", + rd.getName(), LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS); + } } } return deleted; @@ -585,8 +606,8 @@ private static boolean isSystemTemplate(KVMPhysicalDisk disk) { Path propFile = diskPath.getParent().resolve("template.properties"); if (Files.exists(propFile)) { java.util.Properties templateProps = new java.util.Properties(); - try { - templateProps.load(new FileInputStream(propFile.toFile())); + try (FileInputStream in = new FileInputStream(propFile.toFile())) { + templateProps.load(in); String desc = templateProps.getProperty("description"); if (desc != null && desc.startsWith("SystemVM Template")) { return true; diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java index bb354bec9b4b..1bcfaa4ebf7f 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStoragePool.java @@ -228,11 +228,11 @@ public String getHearthBeatPath() { public String createHeartBeatCommand(HAStoragePool pool, String hostPrivateIp, boolean hostValidation) { LOGGER.trace(String.format("Linstor.createHeartBeatCommand: %s, %s, %b", pool.getPoolIp(), hostPrivateIp, hostValidation)); - boolean isStorageNodeUp = checkingHeartBeat(pool, null); + boolean isStorageNodeUp = hasHeartBeat(pool, null); if (!isStorageNodeUp && !hostValidation) { //restart the host LOGGER.debug(String.format("The host [%s] will be restarted because the health check failed for the storage pool [%s]", hostPrivateIp, pool.getPool().getType())); - Script cmd = new Script(pool.getPool().getHearthBeatPath(), Duration.millis(HeartBeatUpdateTimeout), LOGGER); + Script cmd = new Script(pool.getPool().getHearthBeatPath(), Duration.millis(HeartBeatUpdateTimeoutInMs), LOGGER); cmd.add("-c"); cmd.execute(); return "Down"; @@ -258,7 +258,7 @@ static String getHostname() { } @Override - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { + public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) { String hostName; if (host == null) { hostName = localNodeName; @@ -274,7 +274,7 @@ public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { } private String executeDrbdSetupStatus(OutputInterpreter.AllLinesParser parser) { - Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeout), LOGGER); + Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeoutInMs), LOGGER); sc.add("status"); sc.add("--json"); return sc.execute(parser); @@ -329,7 +329,7 @@ private boolean checkDrbdSetupStatusOutput(String output, String otherNodeName) } private String executeDrbdEventsNow(OutputInterpreter.AllLinesParser parser) { - Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeout), LOGGER); + Script sc = new Script("drbdsetup", Duration.millis(HeartBeatUpdateTimeoutInMs), LOGGER); sc.add("events2"); sc.add("--now"); return sc.execute(parser); @@ -369,8 +369,8 @@ private boolean checkHostUpToDateAndConnected(String hostName) { } @Override - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { + public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUUIDListString, String vmActivityCheckPath, long duration) { LOGGER.trace(String.format("Linstor.vmActivityCheck: %s, %s", pool.getPoolIp(), host.getPrivateNetwork().getIp())); - return checkingHeartBeat(pool, host); + return hasHeartBeat(pool, host); } } diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 83dacf74e8df..d7451fab18f8 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -232,6 +232,20 @@ private void deleteResourceDefinition(StoragePoolVO storagePoolVO, String rscDef throw new CloudRuntimeException("Linstor: Unable to delete resource definition: " + rscDefName); } logger.info("Linstor: Deleted resource {}", rscDefName); + + // LINSTOR can return success on the delete API call while the resource lingers in + // DELETING state (peer issues, lost quorum, satellite down). Verify the resource is + // actually gone — if not, log a WARN so operators see it. We deliberately do NOT + // throw here: the volume is already considered gone on the CloudStack side, and + // throwing would leave the CS DB and LINSTOR in different states. + if (!LinstorUtil.waitForResourceDefinitionDeleted(linstorApi, rscDefName, + LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS)) + { + logger.warn("Linstor: resource {} still present {}ms after delete returned success — " + + "may be stuck in DELETING. Check the LINSTOR controller (linstor resource list) " + + "and clear manually if the resource has no live peers.", + rscDefName, LinstorUtil.DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS); + } } catch (ApiException apiEx) { logger.error("Linstor: ApiEx - " + apiEx.getMessage()); diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java index 7c45493dddc4..502bf6d0ca2a 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/util/LinstorUtil.java @@ -401,6 +401,58 @@ public static List getRDListStartingWith(DevelopersApi api, .collect(Collectors.toList()); } + /** + * Default per-call timeout for {@link #waitForResourceDefinitionDeleted}. Long enough for a + * healthy LINSTOR controller to finish a normal delete; short enough not to block the calling + * thread for too long if the delete is genuinely stuck. Used both from the management server + * (e.g. {@code LinstorPrimaryDataStoreDriverImpl}) and from KVM agent paths. + */ + public static final long DEFAULT_RD_DELETE_VERIFY_TIMEOUT_MILLIS = 30_000L; + + /** + * Returns {@code true} if the named resource definition is no longer present on the LINSTOR + * controller. Used after a {@code resourceDefinitionDelete} to verify the delete actually + * completed (LINSTOR can return success on the API call while the resource lingers in + * DELETING state due to peer issues, lost quorum, or down satellites). Uses the + * controller-side name filter rather than scanning every RD on the cluster (cheap even + * when polled once per second from {@link #waitForResourceDefinitionDeleted}). + */ + public static boolean isResourceDefinitionGone(DevelopersApi api, String rscName) throws ApiException { + List matching = + api.resourceDefinitionList(Collections.singletonList(rscName), false, null, null, null); + return matching == null || matching.isEmpty(); + } + + /** + * Polls the controller until the named resource definition is gone or the timeout elapses. + * Returns {@code true} if the resource was confirmed gone, {@code false} if it was still + * present (or the controller kept erroring) at the deadline. Callers should NOT throw on a + * {@code false} return — the upstream API call already reported success and the operator + * may need to investigate manually. Log a WARN with the resource name instead. + */ + public static boolean waitForResourceDefinitionDeleted(DevelopersApi api, String rscName, long timeoutMillis) { + final long deadline = System.currentTimeMillis() + timeoutMillis; + while (true) { + try { + if (isResourceDefinitionGone(api, rscName)) { + return true; + } + } catch (ApiException e) { + LOGGER.debug("LINSTOR delete-verify poll failed for {}: {}", rscName, e.getMessage()); + // Keep polling — controller may be transiently unavailable. + } + if (System.currentTimeMillis() >= deadline) { + return false; + } + try { + Thread.sleep(1_000L); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + return false; + } + } + } + /** * Returns a pair list of resource-definitions with ther 1:1 mapped resource-group objects that start with the * resource name `startWith` @@ -491,7 +543,26 @@ public static ResourceDefinition findResourceDefinition(DevelopersApi api, Strin .filter(rscDfn -> rscDfn.getProps().containsKey(LinstorUtil.getTemplateForAuxPropKey(rscGrpName))) .findFirst(); - return rd.orElseGet(() -> rdsStartingWith.get(0)); + if (rd.isPresent()) { + return rd.get(); + } + // Fallback: no resource has the exact "_cs-template-for-" property. + // This happens when (a) the matched resource is a legacy template created before that + // convention was introduced, or (b) the template was cached by a different resource + // group and the operator hopes to share it. Log so the ambiguity is visible — silent + // first-match fallback has previously routed clones to the wrong template when + // multiple resource groups coexisted on the same controller. + ResourceDefinition fallback = rdsStartingWith.get(0); + LOGGER.warn("LINSTOR findResourceDefinition: no resource for '{}' has the expected " + + "Aux property '{}' for resource group '{}'; falling back to first match '{}' " + + "(present aux properties: {}). If this is wrong, set the property explicitly " + + "or remove the unrelated resource definition.", + rscName, getTemplateForAuxPropKey(rscGrpName), rscGrpName, + fallback.getName(), + fallback.getProps().keySet().stream() + .filter(k -> k.startsWith("Aux/" + CS_TEMPLATE_FOR_PREFIX)) + .collect(Collectors.toList())); + return fallback; } public static boolean isRscDiskless(ResourceWithVolumes rsc) { diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java index cab2820f09ae..aa450a8290fb 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/motion/LinstorDataMotionStrategy.java @@ -314,6 +314,58 @@ private boolean needsExactSizeProp(VolumeInfo srcVolumeInfo) { return true; } + /** + * Verify that the destination KVM host is a registered LINSTOR satellite on the controller + * backing every destination pool involved in this migration. Throws CloudRuntimeException + * with a clear message when it isn't, instead of letting the resource creation later fail + * obscurely inside auto-placement. + * + * Best-effort: a transient controller error during this check does not block the migration + * — we log a warning and let the downstream resource-create surface the real issue. Only a + * confirmed "host not in node list" outcome aborts the migration up-front. + */ + private void verifyDestinationIsLinstorSatellite(Map volumeDataStoreMap, Host destHost) { + if (destHost == null || destHost.getName() == null) { + // Without a destination host name to match, the only sensible thing is to let the + // existing flow run and report whatever it would have reported. + return; + } + for (Map.Entry entry : volumeDataStoreMap.entrySet()) { + DataStore destDataStore = entry.getValue(); + StoragePoolVO destStoragePool = _storagePool.findById(destDataStore.getId()); + if (destStoragePool == null + || destStoragePool.getPoolType() != Storage.StoragePoolType.Linstor) { + continue; + } + DevelopersApi api = LinstorUtil.getLinstorAPI(destStoragePool.getHostAddress()); + try { + List nodes = LinstorUtil.getLinstorNodeNames(api); + if (nodes == null) { + logger.warn("LINSTOR controller {} returned null node list; skipping pre-flight", + destStoragePool.getHostAddress()); + return; + } + if (!nodes.contains(destHost.getName())) { + throw new CloudRuntimeException(String.format( + "Cannot migrate to host '%s': it is not a registered LINSTOR satellite on " + + "controller %s (pool '%s'). Known satellites: %s. Either register the " + + "host with `linstor node create` or pick a different destination.", + destHost.getName(), + destStoragePool.getHostAddress(), + destStoragePool.getName(), + nodes)); + } + } catch (ApiException apiEx) { + // Don't block migration on a transient controller hiccup — log and let the + // downstream resource creation handle the real failure. + logger.warn("LINSTOR pre-flight check could not contact controller {}: {}; " + + "letting downstream resource creation proceed", + destStoragePool.getHostAddress(), apiEx.getBestMessage()); + return; + } + } + } + @Override public void copyAsync(Map volumeDataStoreMap, VirtualMachineTO vmTO, Host srcHost, Host destHost, AsyncCompletionCallback callback) { @@ -323,6 +375,15 @@ public void copyAsync(Map volumeDataStoreMap, VirtualMach String.format("Invalid hypervisor type [%s]. Only KVM supported", srcHost.getHypervisorType())); } + // Pre-flight: verify the destination KVM host is registered as a satellite on the + // LINSTOR controller backing each destination pool. Without this check, resource + // creation falls through to the resource-group's auto-placement filters and may + // either silently place the resource on the wrong node or fail with an opaque + // auto-place error from the LINSTOR API. Failing fast here gives operators a clear + // actionable message instead of having to correlate the live-migration failure with + // an unrelated LINSTOR controller log entry. + verifyDestinationIsLinstorSatellite(volumeDataStoreMap, destHost); + String errMsg = null; VMInstanceVO vmInstance = _vmDao.findById(vmTO.getId()); vmTO.setState(vmInstance.getState()); diff --git a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java index ab5dc03d3431..04100a3c6d38 100644 --- a/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java +++ b/plugins/storage/volume/storpool/src/main/java/com/cloud/hypervisor/kvm/storage/StorPoolStoragePool.java @@ -198,11 +198,11 @@ public String getHearthBeatPath() { @Override public String createHeartBeatCommand(HAStoragePool primaryStoragePool, String hostPrivateIp, boolean hostValidation) { - boolean isStorageNodeUp = checkingHeartBeat(primaryStoragePool, null); + boolean isStorageNodeUp = hasHeartBeat(primaryStoragePool, null); if (!isStorageNodeUp && !hostValidation) { //restart the host logger.debug(String.format("The host [%s] will be restarted because the health check failed for the storage pool [%s]", hostPrivateIp, primaryStoragePool.getPool().getType())); - Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeout, logger); + Script cmd = new Script(primaryStoragePool.getPool().getHearthBeatPath(), HeartBeatUpdateTimeoutInMs, logger); cmd.add("-c"); cmd.execute(); return "Down"; @@ -240,7 +240,7 @@ public static final String getStorPoolConfigParam(String param) { } @Override - public Boolean checkingHeartBeat(HAStoragePool pool, HostTO host) { + public Boolean hasHeartBeat(HAStoragePool pool, HostTO host) { boolean isNodeWorking = false; OutputInterpreter.AllLinesParser parser = new OutputInterpreter.AllLinesParser(); @@ -300,8 +300,8 @@ private String executeStorPoolServiceListCmd(OutputInterpreter.AllLinesParser pa } @Override - public Boolean vmActivityCheck(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUuidListString, String vmActivityCheckPath, long duration) { - return checkingHeartBeat(pool, host); + public Boolean hasVmActivity(HAStoragePool pool, HostTO host, Duration activityScriptTimeout, String volumeUuidListString, String vmActivityCheckPath, long duration) { + return hasHeartBeat(pool, host); } @Override diff --git a/plugins/user-authenticators/ldap/pom.xml b/plugins/user-authenticators/ldap/pom.xml index c9e5a083725c..24a58653e10d 100644 --- a/plugins/user-authenticators/ldap/pom.xml +++ b/plugins/user-authenticators/ldap/pom.xml @@ -172,6 +172,18 @@ org.apache.directory.shared shared-ldap-schema + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcutil-jdk15on + @@ -191,12 +203,40 @@ apacheds-core ${ads.version} test + + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcutil-jdk15on + + org.apache.directory.server apacheds-protocol-ldap ${ads.version} test + + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcutil-jdk15on + + org.apache.directory.server diff --git a/pom.xml b/pom.xml index 17935c526922..a29a656ce512 100644 --- a/pom.xml +++ b/pom.xml @@ -132,7 +132,7 @@ 1.2.8 1.6.4 1.14 - 1.70 + 1.83 3.3.0 8.18 9.2.0 @@ -170,7 +170,7 @@ 0.5.3 1.5.0-b01 0.9.14 - 8.0.33 + 8.4.0 2.0.4 10.1 2.6.6 @@ -191,6 +191,7 @@ 0.5.4 3.1.7 3.25.5 + 8.6.0 @@ -583,17 +584,17 @@ org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on ${cs.bcprov.version} org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on ${cs.bcprov.version} org.bouncycastle - bctls-jdk15on + bctls-jdk18on ${cs.bcprov.version} @@ -668,6 +669,18 @@ org.slf4j log4j-over-slf4j + + org.bouncycastle + bcprov-jdk15on + + + org.bouncycastle + bcpkix-jdk15on + + + org.bouncycastle + bcutil-jdk15on + diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import index a9465f273a3d..cf355e098454 100755 --- a/scripts/util/keystore-cert-import +++ b/scripts/util/keystore-cert-import @@ -70,8 +70,8 @@ elif [ ! -f "$CACERT_FILE" ]; then fi # Import cacerts into the keystore -awk '/-----BEGIN CERTIFICATE-----?/{n++}{print > "cloudca." n }' "$CACERT_FILE" -for caChain in $(ls cloudca.*); do +awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE" +for caChain in $(ls cloudca.* 2>/dev/null); do keytool -delete -noprompt -alias "$caChain" -keystore "$KS_FILE" -storepass "$KS_PASS" > /dev/null 2>&1 || true keytool -import -noprompt -storepass "$KS_PASS" -trustcacerts -alias "$caChain" -file "$caChain" -keystore "$KS_FILE" > /dev/null 2>&1 done @@ -137,6 +137,22 @@ if [ -f "$SYSTEM_FILE" ]; then chmod 644 /usr/local/share/ca-certificates/cloudstack/ca.crt update-ca-certificates > /dev/null 2>&1 || true + # Import CA cert(s) into realhostip.keystore so the SSVM JVM + # (which overrides the truststore via -Djavax.net.ssl.trustStore in _run.sh) + # can trust servers signed by the CloudStack CA + REALHOSTIP_KS_FILE="$(dirname "$(dirname "$PROPS_FILE")")/certs/realhostip.keystore" + REALHOSTIP_PASS="vmops.com" + if [ -f "$REALHOSTIP_KS_FILE" ]; then + awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE" + for caChain in $(ls cloudca.* 2>/dev/null); do + keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \ + -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true + keytool -import -noprompt -trustcacerts -alias "$caChain" -file "$caChain" \ + -keystore "$REALHOSTIP_KS_FILE" -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 + done + rm -f cloudca.* + fi + # Ensure cloud service is running in systemvm if [ "$MODE" == "ssh" ]; then systemctl start cloud > /dev/null 2>&1 diff --git a/scripts/vm/hypervisor/kvm/kvmheartbeat.sh b/scripts/vm/hypervisor/kvm/kvmheartbeat.sh index 9b7eadada69f..1fa49b807769 100755 --- a/scripts/vm/hypervisor/kvm/kvmheartbeat.sh +++ b/scripts/vm/hypervisor/kvm/kvmheartbeat.sh @@ -75,7 +75,7 @@ fi #delete VMs on this mountpoint deleteVMs() { local mountPoint=$1 - vmPids=$(ps aux| grep qemu | grep "$mountPoint" | awk '{print $2}' 2> /dev/null) + vmPids=$(ps aux | grep qemu | grep "$mountPoint" | awk '{print $2}' 2> /dev/null) if [ $? -gt 0 ] then return @@ -93,7 +93,7 @@ deleteVMs() { } #checking is there the same nfs server mounted under $MountPoint? -mounts=$(cat /proc/mounts |grep nfs|grep $MountPoint) +mounts=$(cat /proc/mounts | grep nfs | grep $MountPoint) if [ $? -gt 0 ] then # remount it diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/BaseAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/BaseAllocator.java new file mode 100644 index 000000000000..58fcc62cdc31 --- /dev/null +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/BaseAllocator.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.agent.manager.allocator.impl; + +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.capacity.CapacityManager; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import org.apache.commons.collections.CollectionUtils; + +import javax.inject.Inject; +import java.util.List; + +public abstract class BaseAllocator extends AdapterBase implements HostAllocator { + + @Inject + protected HostDao hostDao; + + @Inject + protected CapacityManager capacityManager; + + protected void retainHostsMatchingServiceOfferingAndTemplateTags(List availableHosts, Host.Type type, long dcId, Long podId, Long clusterId, String offeringHostTag, String templateTag) { + logger.debug("Hosts {} will be checked for template and host tags compatibility.", availableHosts); + + if (offeringHostTag != null) { + logger.debug("Looking for hosts having the tag [{}] specified in the Service Offering.", offeringHostTag); + List hostsWithHostTag = hostDao.listByHostTag(type, clusterId, podId, dcId, offeringHostTag); + logger.debug("Retaining hosts {} because they match the offering host tag {}.", hostsWithHostTag, offeringHostTag); + availableHosts.retainAll(hostsWithHostTag); + } + + if (templateTag != null) { + logger.debug("Looking for hosts having the tag [{}] specified in the Template.", templateTag); + List hostsWithTemplateTag = hostDao.listByHostTag(type, clusterId, podId, dcId, templateTag); + logger.debug("Retaining hosts {} because they match the template tag {}.", hostsWithTemplateTag, templateTag); + availableHosts.retainAll(hostsWithTemplateTag); + } + + logger.debug("Remaining hosts after template tag and host tags validations are {}.", availableHosts); + } + + protected void addHostsBasedOnTagRules(String hostTagOnOffering, List clusterHosts) { + List hostsWithTagRules = hostDao.findHostsWithTagRuleThatMatchComputeOfferingTags(hostTagOnOffering); + + if (CollectionUtils.isEmpty(hostsWithTagRules)) { + logger.info("No hosts found with tag rules matching the compute offering tag [{}].", hostTagOnOffering); + return; + } + + logger.info("Found hosts {} with tag rules matching the compute offering tag [{}].", hostsWithTagRules, hostTagOnOffering); + clusterHosts.addAll(hostsWithTagRules); + } + + /** + * Adds hosts with enough CPU capability and enough CPU capacity to the suitable hosts list. + */ + protected boolean hostHasCpuCapabilityAndCapacity(boolean considerReservedCapacity, ServiceOffering offering, Host host) { + logger.debug("Looking for CPU frequency {} MHz and RAM {} MB.", () -> offering.getCpu() * offering.getSpeed(), offering::getRamSize); + Pair cpuCapabilityAndCapacity = capacityManager.checkIfHostHasCpuCapabilityAndCapacity(host, offering, considerReservedCapacity); + Boolean hasCpuCapability = cpuCapabilityAndCapacity.first(); + Boolean hasCpuCapacity = cpuCapabilityAndCapacity.second(); + + if (hasCpuCapability && hasCpuCapacity) { + logger.debug("Host {} is a suitable host as it has enough CPU capability and CPU capacity.", () -> host); + return true; + } + + logger.debug("Cannot use host {}. Does the host have CPU capability? {}. Does the host have CPU capacity? {}..", () -> host, () -> hasCpuCapability, () -> hasCpuCapacity); + return false; + } + +} diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java index 1110804959ea..8b4ca318bf78 100644 --- a/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocator.java @@ -16,36 +16,17 @@ // under the License. package com.cloud.agent.manager.allocator.impl; -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.firstfitleastconsumed; +import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.random; +import static com.cloud.deploy.DeploymentPlanner.AllocationAlgorithm.userdispersing; -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import com.cloud.gpu.dao.VgpuProfileDao; - -import org.apache.cloudstack.framework.config.dao.ConfigurationDao; -import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; -import org.springframework.stereotype.Component; - -import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.capacity.Capacity; -import com.cloud.capacity.CapacityManager; import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; import com.cloud.configuration.Config; import com.cloud.configuration.ConfigurationManager; -import com.cloud.dc.ClusterDetailsDao; -import com.cloud.dc.dao.ClusterDao; -import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentClusterPlanner; +import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner.ExcludeList; import com.cloud.deploy.FirstFitPlanner; import com.cloud.gpu.GPU; @@ -53,11 +34,9 @@ import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.HostVO; -import com.cloud.host.dao.HostDao; import com.cloud.host.dao.HostDetailsDao; import com.cloud.offering.ServiceOffering; import com.cloud.resource.ResourceManager; -import com.cloud.service.ServiceOfferingDetailsVO; import com.cloud.service.dao.ServiceOfferingDetailsDao; import com.cloud.storage.GuestOSCategoryVO; import com.cloud.storage.GuestOSVO; @@ -66,22 +45,38 @@ import com.cloud.storage.dao.GuestOSDao; import com.cloud.user.Account; import com.cloud.utils.Pair; -import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.StringUtils; import com.cloud.vm.VMInstanceDetailVO; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; -import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; import org.jetbrains.annotations.NotNull; +import org.springframework.stereotype.Component; /** * An allocator that tries to find a fit on a computing host. This allocator does not care whether or not the host supports routing. */ @Component -public class FirstFitAllocator extends AdapterBase implements HostAllocator { - @Inject - protected HostDao _hostDao = null; +public class FirstFitAllocator extends BaseAllocator { @Inject HostDetailsDao _hostDetailsDao = null; @Inject @@ -95,289 +90,183 @@ public class FirstFitAllocator extends AdapterBase implements HostAllocator { @Inject protected ResourceManager _resourceMgr; @Inject - ClusterDao _clusterDao; - @Inject - ClusterDetailsDao _clusterDetailsDao; - @Inject ServiceOfferingDetailsDao _serviceOfferingDetailsDao; @Inject - CapacityManager _capacityMgr; - @Inject CapacityDao _capacityDao; @Inject - VMInstanceDetailsDao _vmInstanceDetailsDao; - @Inject - private VgpuProfileDao vgpuProfileDao; + VMInstanceDetailsDao vmInstanceDetailsDao; boolean _checkHvm = true; + static DecimalFormat decimalFormat = new DecimalFormat("#.##"); @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); + return allocateTo(vmProfile, plan, type, avoid, null, returnUpTo, true); } @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity) { + if (type == Host.Type.Storage) { + return null; + } long dcId = plan.getDataCenterId(); Long podId = plan.getPodId(); Long clusterId = plan.getClusterId(); ServiceOffering offering = vmProfile.getServiceOffering(); - VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); + VMTemplateVO template = (VMTemplateVO) vmProfile.getTemplate(); Account account = vmProfile.getOwner(); - boolean isVMDeployedWithUefi = false; - VMInstanceDetailVO vmInstanceDetailVO = _vmInstanceDetailsDao.findDetail(vmProfile.getId(), "UEFI"); - if(vmInstanceDetailVO != null){ - if ("secure".equalsIgnoreCase(vmInstanceDetailVO.getValue()) || "legacy".equalsIgnoreCase(vmInstanceDetailVO.getValue())) { - isVMDeployedWithUefi = true; - } - } - logger.info(" Guest VM is requested with Custom[UEFI] Boot Type "+ isVMDeployedWithUefi); + String hostTagOnOffering = offering.getHostTag(); + String hostTagOnTemplate = template.getTemplateTag(); + String paramAsStringToLog = String.format("zone [%s], pod [%s], cluster [%s]", dcId, podId, clusterId); + List suitableHosts = retrieveHosts(vmProfile, type, (List) hosts, clusterId, podId, dcId, hostTagOnOffering, hostTagOnTemplate); - if (type == Host.Type.Storage) { - // FirstFitAllocator should be used for user VMs only since it won't care whether the host is capable of routing or not - return new ArrayList<>(); + if (suitableHosts.isEmpty()) { + logger.info("No suitable host found for VM [{}] in {}.", vmProfile, paramAsStringToLog); + return null; } - String paramAsStringToLog = String.format("zone [%s], pod [%s], cluster [%s]", dcId, podId, clusterId); - logger.debug("Looking for hosts in {}", paramAsStringToLog); - String hostTagOnOffering = offering.getHostTag(); - String hostTagOnTemplate = template.getTemplateTag(); - String hostTagUefi = "UEFI"; + if (CollectionUtils.isEmpty(hosts)) { + addHostsToAvoidSet(type, avoid, clusterId, podId, dcId, suitableHosts); + } - boolean hasSvcOfferingTag = hostTagOnOffering != null ? true : false; - boolean hasTemplateTag = hostTagOnTemplate != null ? true : false; + return allocateTo(vmProfile, plan, offering, template, avoid, suitableHosts, returnUpTo, considerReservedCapacity, account); + } - List clusterHosts = new ArrayList<>(); - List hostsMatchingUefiTag = new ArrayList<>(); - if(isVMDeployedWithUefi){ - hostsMatchingUefiTag = _hostDao.listByHostCapability(type, clusterId, podId, dcId, Host.HOST_UEFI_ENABLE); - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagUefi + "' are:" + hostsMatchingUefiTag); - } - } + protected List retrieveHosts(VirtualMachineProfile vmProfile, Type type, List hostsToFilter, Long clusterId, Long podId, long dcId, String hostTagOnOffering, + String hostTagOnTemplate) { + String haVmTag = (String) vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); + List clusterHosts; + if (CollectionUtils.isNotEmpty(hostsToFilter)) { + clusterHosts = new ArrayList<>(hostsToFilter); + } else { + clusterHosts = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId); + } - String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); if (haVmTag != null) { - clusterHosts = _hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag); + clusterHosts.retainAll(hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag)); + } else if (ObjectUtils.allNull(hostTagOnOffering, hostTagOnTemplate)) { + clusterHosts.retainAll(_resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId)); } else { - if (hostTagOnOffering == null && hostTagOnTemplate == null) { - clusterHosts = _resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId); - } else { - List hostsMatchingOfferingTag = new ArrayList<>(); - List hostsMatchingTemplateTag = new ArrayList<>(); - if (hasSvcOfferingTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on SvcOffering:" + hostTagOnOffering); - } - hostsMatchingOfferingTag = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering); - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnOffering + "' are:" + hostsMatchingOfferingTag); - } - } - if (hasTemplateTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on Template:" + hostTagOnTemplate); - } - hostsMatchingTemplateTag = _hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate); - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnTemplate + "' are:" + hostsMatchingTemplateTag); - } - } - - if (hasSvcOfferingTag && hasTemplateTag) { - hostsMatchingOfferingTag.retainAll(hostsMatchingTemplateTag); - if (logger.isDebugEnabled()) { - logger.debug("Found " + hostsMatchingOfferingTag.size() + " Hosts satisfying both tags, host ids are:" + hostsMatchingOfferingTag); - } - - clusterHosts = hostsMatchingOfferingTag; - } else { - if (hasSvcOfferingTag) { - clusterHosts = hostsMatchingOfferingTag; - } else { - clusterHosts = hostsMatchingTemplateTag; - } - } - } + retainHostsMatchingServiceOfferingAndTemplateTags(clusterHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); } - if (isVMDeployedWithUefi) { - clusterHosts.retainAll(hostsMatchingUefiTag); - } + filterHostsWithUefiEnabled(type, vmProfile, clusterId, podId, dcId, clusterHosts); - clusterHosts.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering)); + addHostsBasedOnTagRules(hostTagOnOffering, clusterHosts); + return clusterHosts; - if (clusterHosts.isEmpty()) { - logger.warn("No suitable host found for VM [{}] with tags {} in {}.", vmProfile, hostTagOnOffering, paramAsStringToLog); - return null; - } - // add all hosts that we are not considering to the avoid list - List allhostsInCluster = _hostDao.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId, null); - allhostsInCluster.removeAll(clusterHosts); + } - logger.debug(() -> String.format("Adding hosts [%s] to the avoid set because these hosts do not support HA.", - ReflectionToStringBuilderUtils.reflectOnlySelectedFields(allhostsInCluster, "uuid", "name"))); + /** + * Add all hosts to the avoid set that were not considered during the allocation + */ + protected void addHostsToAvoidSet(Type type, ExcludeList avoid, Long clusterId, Long podId, long dcId, List suitableHosts) { + List allHostsInCluster = hostDao.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId, null); - for (HostVO host : allhostsInCluster) { + allHostsInCluster.removeAll(suitableHosts); + + logger.debug("Adding hosts [{}] to the avoid set because these hosts were not considered for allocation.", + () -> ReflectionToStringBuilderUtils.reflectOnlySelectedFields(allHostsInCluster, "uuid", "name")); + + for (HostVO host : allHostsInCluster) { avoid.addHost(host.getId()); } - - return allocateTo(vmProfile, plan, offering, template, avoid, clusterHosts, returnUpTo, considerReservedCapacity, account); } - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - long dcId = plan.getDataCenterId(); - Long podId = plan.getPodId(); - Long clusterId = plan.getClusterId(); - ServiceOffering offering = vmProfile.getServiceOffering(); - VMTemplateVO template = (VMTemplateVO)vmProfile.getTemplate(); - Account account = vmProfile.getOwner(); - List suitableHosts = new ArrayList<>(); - List hostsCopy = new ArrayList<>(hosts); + protected void filterHostsWithUefiEnabled(Type type, VirtualMachineProfile vmProfile, Long clusterId, Long podId, long dcId, List clusterHosts) { + VMInstanceDetailVO vmInstanceDetailVO = vmInstanceDetailsDao.findDetail(vmProfile.getId(), "UEFI"); - if (type == Host.Type.Storage) { - // FirstFitAllocator should be used for user VMs only since it won't care whether the host is capable of - // routing or not. - return suitableHosts; + if (vmInstanceDetailVO == null) { + return; } - String hostTagOnOffering = offering.getHostTag(); - String hostTagOnTemplate = template.getTemplateTag(); - boolean hasSvcOfferingTag = hostTagOnOffering != null ? true : false; - boolean hasTemplateTag = hostTagOnTemplate != null ? true : false; - - String haVmTag = (String)vmProfile.getParameter(VirtualMachineProfile.Param.HaTag); - if (haVmTag != null) { - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, haVmTag)); - } else { - if (hostTagOnOffering == null && hostTagOnTemplate == null) { - hostsCopy.retainAll(_resourceMgr.listAllUpAndEnabledNonHAHosts(type, clusterId, podId, dcId)); - } else { - if (hasSvcOfferingTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on SvcOffering:" + hostTagOnOffering); - } - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering)); - - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnOffering + "' are:" + hostsCopy); - } - } - - if (hasTemplateTag) { - if (logger.isDebugEnabled()) { - logger.debug("Looking for hosts having tag specified on Template:" + hostTagOnTemplate); - } - - hostsCopy.retainAll(_hostDao.listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate)); - - if (logger.isDebugEnabled()) { - logger.debug("Hosts with tag '" + hostTagOnTemplate + "' are:" + hostsCopy); - } - } - } + if (!StringUtils.equalsAnyIgnoreCase(vmInstanceDetailVO.getValue(), ApiConstants.BootMode.SECURE.toString(), ApiConstants.BootMode.LEGACY.toString())) { + return; } - hostsCopy.addAll(_hostDao.findHostsWithTagRuleThatMatchComputeOferringTags(hostTagOnOffering)); + logger.info("Guest VM is requested with Custom[UEFI] Boot Type enabled."); - if (!hostsCopy.isEmpty()) { - suitableHosts = allocateTo(vmProfile, plan, offering, template, avoid, hostsCopy, returnUpTo, considerReservedCapacity, account); - } + List hostsMatchingUefiTag = hostDao.listByHostCapability(type, clusterId, podId, dcId, Host.HOST_UEFI_ENABLE); - return suitableHosts; + logger.debug("Hosts with UEFI enabled are {}.", hostsMatchingUefiTag); + clusterHosts.retainAll(hostsMatchingUefiTag); } protected List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, ServiceOffering offering, VMTemplateVO template, ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity, Account account) { + boolean considerReservedCapacity, Account account) { String vmAllocationAlgorithm = DeploymentClusterPlanner.VmAllocationAlgorithm.value(); - if (vmAllocationAlgorithm.equals("random")) { - // Shuffle this so that we don't check the hosts in the same order. + if (random.toString().equals(vmAllocationAlgorithm)) { Collections.shuffle(hosts); - } else if (vmAllocationAlgorithm.equals("userdispersing")) { + } else if (userdispersing.toString().equals(vmAllocationAlgorithm)) { hosts = reorderHostsByNumberOfVms(plan, hosts, account); - } else if(vmAllocationAlgorithm.equals("firstfitleastconsumed")){ + } else if (firstfitleastconsumed.toString().equals(vmAllocationAlgorithm)) { hosts = reorderHostsByCapacity(plan, hosts); } - if (logger.isDebugEnabled()) { - logger.debug("FirstFitAllocator has " + hosts.size() + " hosts to check for allocation: " + hosts); - } - - // We will try to reorder the host lists such that we give priority to hosts that have - // the minimums to support a VM's requirements + logger.debug("FirstFitAllocator has {} hosts to check for allocation {}.", hosts.size(), hosts); hosts = prioritizeHosts(template, offering, hosts); + logger.debug("Found {} hosts for allocation after prioritization: {}.", hosts.size(), hosts); - if (logger.isDebugEnabled()) { - logger.debug("Found " + hosts.size() + " hosts for allocation after prioritization: " + hosts); - } + List suitableHosts = checkHostsCompatibilities(offering, vmProfile, avoid, hosts, returnUpTo, considerReservedCapacity); + logger.debug("Host Allocator returning {} suitable hosts.", suitableHosts.size()); - if (logger.isDebugEnabled()) { - logger.debug("Looking for speed=" + (offering.getCpu() * offering.getSpeed()) + "Mhz, Ram=" + offering.getRamSize() + " MB"); - } + return suitableHosts; + } - long serviceOfferingId = offering.getId(); + protected List checkHostsCompatibilities(ServiceOffering offering, VirtualMachineProfile vmProfile, ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { List suitableHosts = new ArrayList<>(); - ServiceOfferingDetailsVO offeringDetails = null; + logger.debug("Checking compatibility for the following hosts {}.", suitableHosts); for (Host host : hosts) { if (suitableHosts.size() == returnUpTo) { break; } + if (avoid.shouldAvoid(host)) { - if (logger.isDebugEnabled()) { - logger.debug("Host: {} is in avoid set, skipping this and trying other available hosts", host); - } + logger.debug("Host [{}] is in avoid set, skipping this and trying other available hosts", host); continue; } - //find number of guest VMs occupying capacity on this host. - if (_capacityMgr.checkIfHostReachMaxGuestLimit(host)) { + if (capacityManager.checkIfHostReachMaxGuestLimit(host)) { logger.debug("Adding host [{}] to the avoid set because this host already has the max number of running (user and/or system) VMs.", host); avoid.addHost(host.getId()); continue; } - // Check if GPU device is required by offering and host has the availability - if (_resourceMgr.isGPUDeviceAvailable(offering, host, vmProfile.getId())) { - logger.debug("Host [{}] has required GPU devices available.", host); - } else { - // If GPU is not available, skip this host - logger.debug("Adding host [{}] to avoid set, because this host does not have required GPU devices available.", host); - avoid.addHost(host.getId()); + if (offeringRequestedVGpuAndHostDoesNotHaveIt(offering, vmProfile, avoid, host)) { continue; } - Pair cpuCapabilityAndCapacity = _capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(host, offering, considerReservedCapacity); - if (cpuCapabilityAndCapacity.first() && cpuCapabilityAndCapacity.second()) { - if (logger.isDebugEnabled()) { - logger.debug("Found a suitable host, adding to list: {}", host); - } + if (hostHasCpuCapabilityAndCapacity(considerReservedCapacity, offering, host)) { suitableHosts.add(host); - } else { - if (logger.isDebugEnabled()) { - logger.debug("Not using host {}; host has cpu capability? {}, host has capacity?{}", - host, cpuCapabilityAndCapacity.first(), cpuCapabilityAndCapacity.second()); - } - avoid.addHost(host.getId()); + continue; } + avoid.addHost(host.getId()); } + return suitableHosts; + } - if (logger.isDebugEnabled()) { - logger.debug("Host Allocator returning " + suitableHosts.size() + " suitable hosts"); - } + protected boolean offeringRequestedVGpuAndHostDoesNotHaveIt(ServiceOffering offering, VirtualMachineProfile vmProfile, ExcludeList avoid, Host host) { + if (_resourceMgr.isGPUDeviceAvailable(offering, host, vmProfile.getId())) { + logger.debug("Host [{}] has required GPU devices available.", () -> host); + return false; + } - return suitableHosts; + logger.debug("Adding host [{}] to avoid set, because this host does not have required GPU devices available.", () -> host); + avoid.addHost(host.getId()); + return true; } - // Reorder hosts in the decreasing order of free capacity. + /** + * Reorder hosts in the decreasing order of free capacity. + */ private List reorderHostsByCapacity(DeploymentPlan plan, List hosts) { Long zoneId = plan.getDataCenterId(); Long clusterId = plan.getClusterId(); @@ -388,26 +277,10 @@ private List reorderHostsByCapacity(DeploymentPlan plan, List decimalFormat.format(entry.getValue() * 100) + "%", (e1, e2) -> e1, LinkedHashMap::new)); - if (logger.isDebugEnabled()) { - logger.debug("List of hosts: [{}] in descending order of free capacity (percentage) in the cluster: {}", - hostIdsByFreeCapacity, sortedHostByCapacity); - } - - //now filter the given list of Hosts by this ordered list - Map hostMap = new HashMap<>(); - for (Host host : hosts) { - hostMap.put(host.getId(), host); - } - List matchingHostIds = new ArrayList<>(hostMap.keySet()); - - hostIdsByFreeCapacity.retainAll(matchingHostIds); - - List reorderedHosts = new ArrayList<>(); - for(Long id: hostIdsByFreeCapacity){ - reorderedHosts.add(hostMap.get(id)); - } + logger.debug("List of hosts: [{}] in descending order of free capacity (percentage) in the cluster: {}.", + hostIdsByFreeCapacity, sortedHostByCapacity); - return reorderedHosts; + return filterHosts(hosts, hostIdsByFreeCapacity); } private Pair, Map> getOrderedHostsByCapacity(Long zoneId, Long clusterId) { @@ -450,116 +323,154 @@ private List reorderHostsByNumberOfVms(DeploymentPlan plan, List Long clusterId = plan.getClusterId(); List hostIdsByVmCount = _vmInstanceDao.listHostIdsByVmCount(dcId, podId, clusterId, account.getAccountId()); - if (logger.isDebugEnabled()) { - logger.debug("List of hosts in ascending order of number of VMs: " + hostIdsByVmCount); - } + logger.debug("List of hosts in ascending order of number of VMs: {}.", hostIdsByVmCount); - //now filter the given list of Hosts by this ordered list + return filterHosts(hosts, hostIdsByVmCount); + } + + /** + * Filter the given list of Hosts considering the ordered list + */ + private List filterHosts(List hosts, List orderedHostIdsList) { Map hostMap = new HashMap<>(); + for (Host host : hosts) { hostMap.put(host.getId(), host); } List matchingHostIds = new ArrayList<>(hostMap.keySet()); - - hostIdsByVmCount.retainAll(matchingHostIds); + orderedHostIdsList.retainAll(matchingHostIds); List reorderedHosts = new ArrayList<>(); - for (Long id : hostIdsByVmCount) { + for(Long id: orderedHostIdsList){ reorderedHosts.add(hostMap.get(id)); } return reorderedHosts; } - @Override - public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - // currently we do no special checks to rule out a VM being upgradable to an offering, so - // return true - return true; - } - + /** + * Reorder the host list giving priority to hosts that have the minimum to support the VM's requirements. + */ protected List prioritizeHosts(VMTemplateVO template, ServiceOffering offering, List hosts) { if (template == null) { return hosts; } - // Determine the guest OS category of the template - String templateGuestOSCategory = getTemplateGuestOSCategory(template); + List hostsToCheck = filterHostWithNoHvmIfTemplateRequested(template, hosts); List prioritizedHosts = new ArrayList<>(); - List noHvmHosts = new ArrayList<>(); + List highPriorityHosts = new ArrayList<>(); + List lowPriorityHosts = new ArrayList<>(); + + prioritizeHostsWithMatchingGuestOs(template, hostsToCheck, highPriorityHosts, lowPriorityHosts); + hostsToCheck.removeAll(highPriorityHosts); + hostsToCheck.removeAll(lowPriorityHosts); + + prioritizeHostsByHvmCapability(template, hostsToCheck, prioritizedHosts); + prioritizedHosts.addAll(0, highPriorityHosts); + prioritizedHosts.addAll(lowPriorityHosts); + + prioritizeHostsByGpuEnabled(offering, prioritizedHosts); + + return prioritizedHosts; + } + - // If a template requires HVM and a host doesn't support HVM, remove it from consideration + /** + * If a template requires HVM and a host doesn't support HVM, remove it from consideration. + */ + protected List filterHostWithNoHvmIfTemplateRequested(VMTemplateVO template, List hosts) { List hostsToCheck = new ArrayList<>(); - if (template.isRequiresHvm()) { - for (Host host : hosts) { - if (hostSupportsHVM(host)) { - hostsToCheck.add(host); - } else { - noHvmHosts.add(host); - } - } - } else { + + if (!template.isRequiresHvm()) { + logger.debug("Template [{}] does not require HVM, therefore, the hosts {} will not be checked for HVM compatibility.", template, hostsToCheck); hostsToCheck.addAll(hosts); + return hostsToCheck; } - if (logger.isDebugEnabled()) { - if (noHvmHosts.size() > 0) { - logger.debug("Not considering hosts: " + noHvmHosts + " to deploy template: " + template + " as they are not HVM enabled"); + List noHvmHosts = new ArrayList<>(); + logger.debug("Template [{}] requires HVM, therefore, the hosts %s will be checked for HVM compatibility.", template, hostsToCheck); + + for (Host host : hosts) { + if (hostSupportsHVM(host)) { + hostsToCheck.add(host); + } else { + noHvmHosts.add(host); } } - // If a host is tagged with the same guest OS category as the template, move it to a high priority list - // If a host is tagged with a different guest OS category than the template, move it to a low priority list - List highPriorityHosts = new ArrayList<>(); - List lowPriorityHosts = new ArrayList<>(); - for (Host host : hostsToCheck) { - String hostGuestOSCategory = getHostGuestOSCategory(host); - if (hostGuestOSCategory == null) { - continue; - } else if (templateGuestOSCategory != null && templateGuestOSCategory.equals(hostGuestOSCategory)) { - highPriorityHosts.add(host); - } else { - lowPriorityHosts.add(host); + + if (!noHvmHosts.isEmpty()) { + logger.debug("Not considering hosts {} to deploy VM using template {} as they are not HVM enabled.", noHvmHosts, template); + } + + return hostsToCheck; + } + + + /** + * If service offering did not request for vGPU, then move all host with GPU to the end of the host priority list. + */ + protected void prioritizeHostsByGpuEnabled(ServiceOffering offering, List prioritizedHosts) { + boolean serviceOfferingRequestedVGpu = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) != null || offering.getVgpuProfileId() != null; + + if (serviceOfferingRequestedVGpu) { + return; + } + + List gpuEnabledHosts = new ArrayList<>(); + + for (Host host : prioritizedHosts) { + if (_resourceMgr.isHostGpuEnabled(host.getId())) { + gpuEnabledHosts.add(host); } } - hostsToCheck.removeAll(highPriorityHosts); - hostsToCheck.removeAll(lowPriorityHosts); + if (!gpuEnabledHosts.isEmpty()) { + prioritizedHosts.removeAll(gpuEnabledHosts); + prioritizedHosts.addAll(gpuEnabledHosts); + } + } - // Prioritize the remaining hosts by HVM capability + /** + * Prioritize remaining host by HVM capability. + * + *
    + *
  • If host and template both do not support HVM, put it at the start of the list.
  • + *
  • If the template doesn't require HVM, but the machine supports it, append it to the list.
  • + *
+ */ + protected void prioritizeHostsByHvmCapability(VMTemplateVO template, List hostsToCheck, List prioritizedHosts) { for (Host host : hostsToCheck) { if (!template.isRequiresHvm() && !hostSupportsHVM(host)) { - // Host and template both do not support hvm, put it as first consideration prioritizedHosts.add(0, host); } else { - // Template doesn't require hvm, but the machine supports it, make it last for consideration prioritizedHosts.add(host); } } + } - // Merge the lists - prioritizedHosts.addAll(0, highPriorityHosts); - prioritizedHosts.addAll(lowPriorityHosts); - // if service offering is not GPU enabled then move all the GPU enabled hosts to the end of priority list. - if (_serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString()) == null && offering.getVgpuProfileId() == null) { + /** + *
    + *
  • If a host is tagged with the same guest OS category as the template, move it to a high priority list.
  • + *
  • If a host is tagged with a different guest OS category than the template, move it to a low priority list.
  • + *
+ */ + protected void prioritizeHostsWithMatchingGuestOs(VMTemplateVO template, List hostsToCheck, List highPriorityHosts, List lowPriorityHosts) { + String templateGuestOSCategory = getTemplateGuestOSCategory(template); - List gpuEnabledHosts = new ArrayList<>(); - // Check for GPU enabled hosts. - for (Host host : prioritizedHosts) { - if (_resourceMgr.isHostGpuEnabled(host.getId())) { - gpuEnabledHosts.add(host); - } - } - // Move GPU enabled hosts to the end of list - if(!gpuEnabledHosts.isEmpty()) { - prioritizedHosts.removeAll(gpuEnabledHosts); - prioritizedHosts.addAll(gpuEnabledHosts); + for (Host host : hostsToCheck) { + String hostGuestOSCategory = getHostGuestOSCategory(host); + + if (StringUtils.equals(templateGuestOSCategory, hostGuestOSCategory)) { + highPriorityHosts.add(host); + } else if (hostGuestOSCategory != null) { + lowPriorityHosts.add(host); } } - return prioritizedHosts; } + protected boolean hostSupportsHVM(Host host) { if (!_checkHvm) { return true; @@ -621,19 +532,8 @@ public boolean configure(String name, Map params) throws Configu if (_configDao != null) { Map configs = _configDao.getConfiguration(params); String value = configs.get("xenserver.check.hvm"); - _checkHvm = value == null ? true : Boolean.parseBoolean(value); + _checkHvm = value == null || Boolean.parseBoolean(value); } return true; } - - @Override - public boolean start() { - return true; - } - - @Override - public boolean stop() { - return true; - } - } diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java new file mode 100644 index 000000000000..96b4255b876d --- /dev/null +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/RandomAllocator.java @@ -0,0 +1,132 @@ +// 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.agent.manager.allocator.impl; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.inject.Inject; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.stereotype.Component; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.host.Host; +import com.cloud.host.Host.Type; +import com.cloud.host.HostVO; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.VMTemplateVO; +import com.cloud.vm.VirtualMachineProfile; + +@Component +public class RandomAllocator extends BaseAllocator { + @Inject + private ResourceManager _resourceMgr; + + protected List findSuitableHosts(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity) { + if (type == Host.Type.Storage) { + return null; + } + + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + ServiceOffering offering = vmProfile.getServiceOffering(); + + String offeringHostTag = offering.getHostTag(); + VMTemplateVO template = (VMTemplateVO) vmProfile.getTemplate(); + logger.debug("Looking for hosts in zone [{}], pod [{}], cluster [{}].", dcId, podId, clusterId); + + List availableHosts = retrieveHosts(type, (List) hosts, template, offeringHostTag, clusterId, podId, dcId); + + if (availableHosts.isEmpty()) { + logger.info("No suitable host found for VM [{}] in zone [{}], pod [{}], cluster [{}].", vmProfile, dcId, podId, clusterId); + return null; + } + + return filterAvailableHosts(avoid, returnUpTo, considerReservedCapacity, availableHosts, offering); + } + + protected List filterAvailableHosts(ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity, List availableHosts, ServiceOffering offering) { + logger.debug("Random Allocator found [{}] available hosts. They will be checked if they are in the avoid set and for CPU capability and capacity.", availableHosts::size); + List suitableHosts = new ArrayList<>(); + + Collections.shuffle(availableHosts); + for (Host host : availableHosts) { + if (suitableHosts.size() == returnUpTo) { + break; + } + + if (avoid.shouldAvoid(host)) { + logger.debug("Host [{}] is in the avoid set, skipping it and trying other available hosts.", () -> host); + continue; + } + + if (!hostHasCpuCapabilityAndCapacity(considerReservedCapacity, offering, host)) { + continue; + } + + logger.debug("Found the suitable host [{}], adding to list.", () -> host); + suitableHosts.add(host); + } + + logger.debug("Random Host Allocator returning {} suitable hosts.", suitableHosts::size); + return suitableHosts; + } + + /** + * @return all computing hosts, regardless of whether they support routing. + */ + protected List retrieveHosts(Type type, List hosts, VMTemplateVO template, String offeringHostTag, Long clusterId, Long podId, long dcId) { + List availableHosts; + String templateTag = template.getTemplateTag(); + + if (CollectionUtils.isNotEmpty(hosts)) { + availableHosts = new ArrayList<>(hosts); + } else { + availableHosts = _resourceMgr.listAllUpAndEnabledHosts(type, clusterId, podId, dcId); + } + + if (ObjectUtils.anyNotNull(offeringHostTag, templateTag)) { + retainHostsMatchingServiceOfferingAndTemplateTags(availableHosts, type, dcId, podId, clusterId, offeringHostTag, templateTag); + } else { + List hostsWithNoRuleTag = hostDao.listAllHostsThatHaveNoRuleTag(type, clusterId, podId, dcId); + logger.debug("Retaining hosts {} because they do not have rule tags.", hostsWithNoRuleTag); + availableHosts.retainAll(hostsWithNoRuleTag); + } + + addHostsBasedOnTagRules(offeringHostTag, availableHosts); + + return availableHosts; + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { + return allocateTo(vmProfile, plan, type, avoid, null, returnUpTo, true); + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, + boolean considerReservedCapacity) { + return findSuitableHosts(vmProfile, plan, type, avoid, hosts, returnUpTo, considerReservedCapacity); + } +} diff --git a/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java b/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java index fd8e65e24761..04a0b182b7b1 100644 --- a/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java +++ b/server/src/main/java/com/cloud/agent/manager/allocator/impl/TestingAllocator.java @@ -28,32 +28,24 @@ import com.cloud.host.Host; import com.cloud.host.Host.Type; import com.cloud.host.dao.HostDao; -import com.cloud.offering.ServiceOffering; import com.cloud.utils.component.AdapterBase; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; public class TestingAllocator extends AdapterBase implements HostAllocator { @Inject HostDao _hostDao; - Long _computingHost; Long _storageHost; Long _routingHost; @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, true); + return allocateTo(vmProfile, plan, type, avoid, null, returnUpTo, true); } @Override public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, List hosts, int returnUpTo, - boolean considerReservedCapacity) { - return allocateTo(vmProfile, plan, type, avoid, returnUpTo, considerReservedCapacity); - } - - @Override - public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Type type, ExcludeList avoid, int returnUpTo, boolean considerReservedCapacity) { - List availableHosts = new ArrayList(); + boolean considerReservedCapacity) { + List availableHosts = new ArrayList<>(); Host host = null; if (type == Host.Type.Routing && _routingHost != null) { host = _hostDao.findById(_routingHost); @@ -66,13 +58,6 @@ public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan pla return availableHosts; } - @Override - public boolean isVirtualMachineUpgradable(VirtualMachine vm, ServiceOffering offering) { - // currently we do no special checks to rule out a VM being upgradable to an offering, so - // return true - return true; - } - @Override public boolean configure(String name, Map params) { String value = (String)params.get(Host.Type.Routing.toString()); diff --git a/server/src/main/java/com/cloud/api/ApiResponseHelper.java b/server/src/main/java/com/cloud/api/ApiResponseHelper.java index 11ac3d639e99..a8551b4c6693 100644 --- a/server/src/main/java/com/cloud/api/ApiResponseHelper.java +++ b/server/src/main/java/com/cloud/api/ApiResponseHelper.java @@ -2890,6 +2890,11 @@ public NetworkResponse createNetworkResponse(ResponseView view, Network network) } } + if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN && + network.getVpcId() == null && network.getGuestType() == Network.GuestType.Isolated) { + response.setKeepMacAddressOnPublicNic(network.getKeepMacAddressOnPublicNic()); + } + response.setObjectName("network"); return response; } @@ -3653,6 +3658,9 @@ public VpcResponse createVpcResponse(ResponseView view, Vpc vpc) { } } + if (CallContext.current().getCallingAccount().getType() == Account.Type.ADMIN) { + response.setKeepMacAddressOnPublicNic(vpc.getKeepMacAddressOnPublicNic()); + } response.setObjectName("vpc"); return response; } diff --git a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java index 2ceaffd69d1f..051af377c6d0 100644 --- a/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java +++ b/server/src/main/java/com/cloud/api/query/QueryManagerImpl.java @@ -5034,16 +5034,13 @@ private Pair, Integer> searchForTemplatesInternal(Long temp ex.addProxyObject(template.getUuid(), "templateId"); throw ex; } + if (!template.isPublicTemplate() && caller.getType() == Account.Type.DOMAIN_ADMIN) { Account template_acc = accountMgr.getAccount(template.getAccountId()); DomainVO domain = _domainDao.findById(template_acc.getDomainId()); accountMgr.checkAccess(caller, domain); - } - - // if template is not public, perform permission check here - else if (!template.isPublicTemplate() && caller.getType() != Account.Type.ADMIN) { - accountMgr.checkAccess(caller, null, false, template); - } else if (template.isPublicTemplate()) { + } else if (template.isPublicTemplate() || caller.getType() != Account.Type.ADMIN) { + // if template is not public or non-admin caller, perform permission check here accountMgr.checkAccess(caller, null, false, template); } diff --git a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java index 2940f900b081..3be7384ffb71 100644 --- a/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java +++ b/server/src/main/java/com/cloud/capacity/CapacityManagerImpl.java @@ -1214,6 +1214,6 @@ public String getConfigComponentName() { public ConfigKey[] getConfigKeys() { return new ConfigKey[] {CpuOverprovisioningFactor, MemOverprovisioningFactor, StorageCapacityDisableThreshold, StorageOverprovisioningFactor, StorageAllocatedCapacityDisableThreshold, StorageOperationsExcludeCluster, ImageStoreNFSVersion, SecondaryStorageCapacityThreshold, - StorageAllocatedCapacityDisableThresholdForVolumeSize, CapacityCalculateWorkers }; + StorageAllocatedCapacityDisableThresholdForVolumeSize, CapacityCalculateWorkers, KvmMemoryDynamicScalingCapacity, KvmCpuDynamicScalingCapacity }; } } diff --git a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java index f163a3d52a5a..7fb4a97e149b 100644 --- a/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java +++ b/server/src/main/java/com/cloud/deploy/DeploymentPlanningManagerImpl.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; @@ -1714,20 +1713,19 @@ protected List findSuitableHosts(VirtualMachineProfile vmProfile, Deployme @Override public void reorderHostsByPriority(Map priorities, List hosts) { - logger.debug("Re-ordering hosts {} by priorities {}", hosts, priorities); + if (CollectionUtils.isEmpty(hosts)){ + logger.debug("Hosts list is empty; therefore, there is nothing to reorder."); + return; + } + logger.info("Re-ordering hosts [{}] by priorities [{}].", hosts, priorities); hosts.removeIf(host -> DataCenterDeployment.PROHIBITED_HOST_PRIORITY.equals(getHostPriority(priorities, host.getId()))); + hosts.sort((host1, host2) -> { + int res = getHostPriority(priorities, host1.getId()).compareTo(getHostPriority(priorities, host2.getId())); + return -res; + }); - Collections.sort(hosts, new Comparator<>() { - @Override - public int compare(Host host1, Host host2) { - int res = getHostPriority(priorities, host1.getId()).compareTo(getHostPriority(priorities, host2.getId())); - return -res; - } - } - ); - - logger.debug("Hosts after re-ordering are: {}", hosts); + logger.info("Hosts after re-ordering are: [{}].", hosts); } private Integer getHostPriority(Map priorities, Long hostId) { diff --git a/server/src/main/java/com/cloud/ha/CheckOnAgentInvestigator.java b/server/src/main/java/com/cloud/ha/CheckOnAgentInvestigator.java index d7945ef20776..e0dbc8fcbe11 100644 --- a/server/src/main/java/com/cloud/ha/CheckOnAgentInvestigator.java +++ b/server/src/main/java/com/cloud/ha/CheckOnAgentInvestigator.java @@ -38,7 +38,7 @@ protected CheckOnAgentInvestigator() { } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { return null; } diff --git a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java index c8635397b661..755de00dec26 100644 --- a/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java +++ b/server/src/main/java/com/cloud/ha/HighAvailabilityManagerImpl.java @@ -42,6 +42,9 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.ha.HAConfig; +import org.apache.cloudstack.ha.HAResource; +import org.apache.cloudstack.ha.dao.HAConfigDao; import org.apache.cloudstack.managed.context.ManagedContext; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.management.ManagementServerHost; @@ -223,6 +226,8 @@ public void setHaPlanners(List haPlanners) { @Inject ConfigurationDao _configDao; @Inject + HAConfigDao _haConfigDao; + @Inject VolumeOrchestrationService volumeMgr; String _instance; @@ -237,25 +242,53 @@ public void setHaPlanners(List haPlanners) { long _timeBetweenCleanups; String _haTag = null; + protected HighAvailabilityManagerImpl() { + } + private boolean vmHasPendingHAJob(final List pendingHaWorks, final VMInstanceVO vm) { Optional item = pendingHaWorks.stream() .filter(h -> h.getInstanceId() == vm.getId()) .reduce((first, second) -> second); if (item.isPresent() && (item.get().getTimesTried() < _maxRetries || !item.get().canScheduleNew(_timeBetweenFailures))) { - logger.debug(String.format("Skipping HA on %s as there is already a running HA job for it", vm)); + logger.debug("Skipping HA on {} as there is already a running HA job for it", vm); return true; } return false; } - protected HighAvailabilityManagerImpl() { + private boolean isHostHAInspectionInProgress(long hostId) { + final HAConfig haConfig = _haConfigDao.findHAResource(hostId, HAResource.ResourceType.Host); + if (haConfig == null || !haConfig.isEnabled()) { + return false; + } + + HAConfig.HAState state = haConfig.getState(); + logger.debug("Checking Host HA inspection is in progress or not for the host {} from HAConfig, HA state is {}", hostId, state); + if (state == HAConfig.HAState.Suspect || state == HAConfig.HAState.Checking) { + return true; + } + + if (state == HAConfig.HAState.Recovered || state == HAConfig.HAState.Available) { + // If the host HA state is Recovered, it indicates that the host has restarted successfully. + // If the host HA state is Available, it means the host has restarted successfully and the recovery waiting period has completed. + // In both states, the agent can connect as soon as the host is ready (and can move to Suspect -> Checking HA state if the agent connection fails again before Fencing). + final HostVO host = _hostDao.findById(hostId); + if (host != null && host.getStatus() != Status.Up) { + logger.debug("{} is in {} status and HA state is {}, considering Host HA inspection is still in progress" + + " until we are sure the host is ready after a recovery wait period and agent is connected/Up", host, host.getStatus(), state); + return true; + } + } + + return false; } @Override public Status investigate(final long hostId) { final HostVO host = _hostDao.findById(hostId); if (host == null) { + logger.warn("Host with id {} is removed or doesn't exists.", hostId); return Status.Alert; } @@ -270,7 +303,7 @@ public Status investigate(final long hostId) { Status hostState = null; for (Investigator investigator : investigators) { - hostState = investigator.isAgentAlive(host); + hostState = investigator.getHostAgentStatus(host); if (hostState != null) { if (logger.isDebugEnabled()) { logger.debug("{} was able to determine host {} is in {}", investigator.getName(), host, hostState.toString()); @@ -278,7 +311,7 @@ public Status investigate(final long hostId) { return hostState; } if (logger.isDebugEnabled()) { - logger.debug(investigator.getName() + " unable to determine the state of the host. Moving on."); + logger.debug("{} unable to determine the state of the host. Moving on.", investigator.getName()); } } @@ -570,9 +603,9 @@ private void startVm(VirtualMachine vm, Map } protected Long restart(final HaWorkVO work) { - logger.debug("RESTART with HAWORK"); + logger.debug("RESTART with HA WORK"); List items = _haDao.listFutureHaWorkForVm(work.getInstanceId(), work.getId()); - if (items.size() > 0) { + if (!items.isEmpty()) { StringBuilder str = new StringBuilder("Cancelling this work item because newer ones have been scheduled. Work Ids = ["); for (HaWorkVO item : items) { str.append(item.getId()).append(", "); @@ -583,7 +616,7 @@ protected Long restart(final HaWorkVO work) { } items = _haDao.listRunningHaWorkForVm(work.getInstanceId()); - if (items.size() > 0) { + if (!items.isEmpty()) { StringBuilder str = new StringBuilder("Waiting because there's HA work being executed on an item currently. Work Ids =["); for (HaWorkVO item : items) { str.append(item.getId()).append(", "); @@ -597,21 +630,21 @@ protected Long restart(final HaWorkVO work) { VirtualMachine vm = _itMgr.findById(work.getInstanceId()); if (vm == null) { - logger.info("Unable to find vm: " + vmId); + logger.info("Unable to find vm: {}", vmId); return null; } if (checkAndCancelWorkIfNeeded(work)) { return null; } - logger.info("HA on " + vm); + logger.info("HA on {}", vm); if (vm.getState() != work.getPreviousState() || vm.getUpdated() != work.getUpdateTime()) { - logger.info("VM " + vm + " has been changed. Current State = " + vm.getState() + " Previous State = " + work.getPreviousState() + " last updated = " + - vm.getUpdated() + " previous updated = " + work.getUpdateTime()); + logger.info("VM {} has been changed. Current State = {} Previous State = {} last updated = {} previous updated = {}", + vm, vm.getState(), work.getPreviousState(), vm.getUpdated(), work.getUpdateTime()); return null; } if (vm.getHostId() != null && !vm.getHostId().equals(work.getHostId())) { - logger.info("VM " + vm + " has been changed. Current host id = " + vm.getHostId() + " Previous host id = " + work.getHostId()); + logger.info("VM {} has been changed. Current host id = {} Previous host id = {}", vm, vm.getHostId(), work.getHostId()); return null; } @@ -628,10 +661,13 @@ protected Long restart(final HaWorkVO work) { boolean isHostRemoved = false; if (host == null) { host = _hostDao.findByIdIncludingRemoved(work.getHostId()); - if (host != null) { - logger.debug("VM {} is now no longer on host {} as the host is removed", vm, host); - isHostRemoved = true; + if (host == null) { + logger.debug("VM {} is now no longer on host {}, the host doesn't exist", vm, work.getHostId()); + return null; } + + logger.debug("VM {} is now no longer on host {} as the host is removed", vm, host); + isHostRemoved = true; } DataCenterVO dcVO = _dcDao.findById(host.getDataCenterId()); @@ -652,40 +688,39 @@ protected Long restart(final HaWorkVO work) { try { alive = investigator.isVmAlive(vm, host); - logger.info(investigator.getName() + " found " + vm + " to be alive? " + alive); + logger.info("{} found {} to be alive? {}", investigator.getName(), vm, alive); break; } catch (UnknownVM e) { - logger.info(investigator.getName() + " could not find " + vm); + logger.info("{} could not find {}", investigator.getName(), vm); } } boolean fenced = false; if (alive == null) { - logger.debug("Fencing off VM that we don't know the state of"); + logger.debug("Fencing off VM {} that we don't know the state of", vm); for (FenceBuilder fb : fenceBuilders) { Boolean result = fb.fenceOff(vm, host); - logger.info("Fencer " + fb.getName() + " returned " + result); + logger.info("Fencer {} returned {}", fb.getName(), result); if (result != null && result) { fenced = true; break; } } - } else if (!alive) { fenced = true; } else { - logger.debug("VM {} is found to be alive by {}", vm, investigator.getName()); + logger.debug("VM {} is found to be alive by {} on host {}", vm, investigator.getName(), host); if (host.getStatus() == Status.Up) { - logger.info(vm + " is alive and host is up. No need to restart it."); + logger.info("{} is alive and host {} is up. No need to restart it.", vm, host); return null; } else { - logger.debug("Rescheduling because the host is not up but the vm is alive"); + logger.debug("Rescheduling because the host {} is not up but the vm {} is alive", host, vm); return (System.currentTimeMillis() >> 10) + _investigateRetryInterval; } } if (!fenced) { - logger.debug("We were unable to fence off the VM " + vm); + logger.debug("We were unable to fence off the VM {}", vm); _alertMgr.sendAlert(alertType, vm.getDataCenterId(), vm.getPodIdToDeployIn(), "Unable to restart " + vm.getHostName() + " which was running on host " + hostDesc, "Insufficient capacity to restart VM, name: " + vm.getHostName() + ", id: " + vmId + " which was running on host " + hostDesc); @@ -728,15 +763,15 @@ protected Long restart(final HaWorkVO work) { if (!ForceHA.value() && !vm.isHaEnabled()) { if (logger.isDebugEnabled()) { - logger.debug("VM is not HA enabled so we're done."); + logger.debug("VM {} is not HA enabled so we're done.", vm); } return null; // VM doesn't require HA } - if ((host == null || host.getRemoved() != null || host.getState() != Status.Up) + if ((host.getRemoved() != null || host.getState() != Status.Up) && !volumeMgr.canVmRestartOnAnotherServer(vm.getId())) { if (logger.isDebugEnabled()) { - logger.debug("VM can not restart on another server."); + logger.debug("VM {} can not restart on another server.", vm); } return null; } @@ -777,13 +812,13 @@ protected Long restart(final HaWorkVO work) { if (started != null && started.getState() == VirtualMachine.State.Running) { String message = String.format("HA starting VM: %s (%s)", started.getHostName(), started.getInstanceName()); HostVO hostVmHasStarted = _hostDao.findById(started.getHostId()); - logger.info(String.format("HA is now restarting %s on %s", started, hostVmHasStarted)); + logger.info("HA is now restarting {} on {}", started, hostVmHasStarted); _alertMgr.sendAlert(alertType, vm.getDataCenterId(), vm.getPodIdToDeployIn(), message, message); return null; } if (logger.isDebugEnabled()) { - logger.debug("Rescheduling VM " + vm.toString() + " to try again in " + _restartRetryInterval); + logger.debug("Rescheduling VM {} to try again in {}", vm.toString(), _restartRetryInterval); } } catch (final InsufficientCapacityException e) { logger.warn("Unable to restart " + vm.toString() + " due to " + e.getMessage()); @@ -815,6 +850,9 @@ protected boolean checkAndCancelWorkIfNeeded(final HaWorkVO work) { if (!CancellableWorkReasonTypes.contains(work.getReasonType())) { return false; } + if (isHostHAInspectionInProgress(work.getHostId())) { + return false; + } Status hostStatus = investigate(work.getHostId()); if (!Status.Up.equals(hostStatus)) { return false; @@ -825,13 +863,14 @@ protected boolean checkAndCancelWorkIfNeeded(final HaWorkVO work) { } public Long migrate(final HaWorkVO work) { + logger.debug("MIGRATE with HA WORK"); long vmId = work.getInstanceId(); long srcHostId = work.getHostId(); HostVO srcHost = _hostDao.findById(srcHostId); VMInstanceVO vm = _instanceDao.findById(vmId); if (vm == null) { - logger.info("Unable to find vm: " + vmId + ", skipping migrate."); + logger.info("Unable to find vm: {}, skipping migrate.", vmId); return null; } if (checkAndCancelWorkIfNeeded(work)) { @@ -840,11 +879,11 @@ public Long migrate(final HaWorkVO work) { logger.info("Migration attempt: for {} from {}. Starting attempt: {}/{} times.", vm, srcHost, 1 + work.getTimesTried(), _maxRetries); if (VirtualMachine.State.Stopped.equals(vm.getState())) { - logger.info(String.format("vm %s is Stopped, skipping migrate.", vm)); + logger.info("vm {} is Stopped, skipping migrate.", vm); return null; } if (VirtualMachine.State.Running.equals(vm.getState()) && srcHostId != vm.getHostId()) { - logger.info(String.format("VM %s is running on a different host %s, skipping migration", vm, vm.getHostId())); + logger.info("VM {} is running on a different host {}, skipping migration", vm, vm.getHostId()); return null; } @@ -879,7 +918,7 @@ public boolean scheduleDestroy(VMInstanceVO vm, long hostId, ReasonType reasonTy final HaWorkVO work = new HaWorkVO(vm.getId(), vm.getType(), WorkType.Destroy, Step.Scheduled, hostId, vm.getState(), 0, vm.getUpdated(), reasonType); _haDao.persist(work); if (logger.isDebugEnabled()) { - logger.debug("Scheduled " + work.toString()); + logger.debug("{}}", work.toString()); } wakeupWorkers(); return true; @@ -897,7 +936,7 @@ private void stopVMWithCleanup(VirtualMachine vm, VirtualMachine.State state) th } private void destroyVM(VirtualMachine vm, boolean expunge) throws OperationTimedoutException, AgentUnavailableException { - logger.info("Destroying " + vm.toString()); + logger.info("Destroying {}", vm.toString()); if (VirtualMachine.Type.ConsoleProxy.equals(vm.getType())) { consoleProxyManager.destroyProxy(vm.getId()); } else if (VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType())) { @@ -908,9 +947,10 @@ private void destroyVM(VirtualMachine vm, boolean expunge) throws OperationTimed } protected Long destroyVM(final HaWorkVO work) { + logger.debug("DESTROY with HA WORK"); final VirtualMachine vm = _itMgr.findById(work.getInstanceId()); if (vm == null) { - logger.info("No longer can find VM " + work.getInstanceId() + ". Throwing away " + work); + logger.info("No longer can find VM {}. Throwing away {}", work.getInstanceId(), work); return null; } if (checkAndCancelWorkIfNeeded(work)) { @@ -944,20 +984,21 @@ protected Long destroyVM(final HaWorkVO work) { } protected Long stopVM(final HaWorkVO work) throws ConcurrentOperationException { + logger.debug("STOP with HA WORK"); VirtualMachine vm = _itMgr.findById(work.getInstanceId()); if (vm == null) { - logger.info("No longer can find VM " + work.getInstanceId() + ". Throwing away " + work); + logger.info("No longer can find VM {}. Throwing away {}", work.getInstanceId(), work); work.setStep(Step.Done); return null; } if (checkAndCancelWorkIfNeeded(work)) { return null; } - logger.info("Stopping " + vm); + logger.info("Stopping {}", vm); try { if (work.getWorkType() == WorkType.Stop) { _itMgr.advanceStop(vm.getUuid(), false); - logger.info("Successfully stopped " + vm); + logger.info("Successfully stopped {}", vm); return null; } else if (work.getWorkType() == WorkType.CheckStop) { if ((vm.getState() != work.getPreviousState()) || vm.getUpdated() != work.getUpdateTime() || vm.getHostId() == null || @@ -969,7 +1010,7 @@ protected Long stopVM(final HaWorkVO work) throws ConcurrentOperationException { } _itMgr.advanceStop(vm.getUuid(), false); - logger.info("Stop for " + vm + " was successful"); + logger.info("Stop for {} was successful", vm); return null; } else if (work.getWorkType() == WorkType.ForceStop) { if ((vm.getState() != work.getPreviousState()) || vm.getUpdated() != work.getUpdateTime() || vm.getHostId() == null || @@ -981,13 +1022,13 @@ protected Long stopVM(final HaWorkVO work) throws ConcurrentOperationException { } _itMgr.advanceStop(vm.getUuid(), true); - logger.info("Stop for " + vm + " was successful"); + logger.info("Stop for {} was successful", vm); return null; } else { assert false : "Who decided there's other steps but didn't modify the guy who does the work?"; } } catch (final ResourceUnavailableException e) { - logger.debug("Agnet is not available" + e.getMessage()); + logger.debug("Agent is not available" + e.getMessage()); } catch (OperationTimedoutException e) { logger.debug("operation timed out: " + e.getMessage()); } @@ -1043,7 +1084,8 @@ private void processWork(final HaWorkVO work) { try { if (vm != null && !VmHaEnabled.valueIn(vm.getDataCenterId())) { if (logger.isDebugEnabled()) { - logger.debug(String.format("VM high availability manager is disabled, rescheduling the HA work %s, for the VM %s (id) to retry later in case VM high availability manager is enabled on retry attempt", work, vm.getName(), vm.getId())); + logger.debug("VM high availability manager is disabled, rescheduling the HA work {} for the VM {} ({}) " + + "to retry later in case VM high availability manager is enabled on retry attempt", work, vm.getName(), vm.getId()); } long nextTime = getRescheduleTime(wt); rescheduleWork(work, nextTime); @@ -1065,13 +1107,13 @@ private void processWork(final HaWorkVO work) { } if (nextTime == null) { - logger.info("Completed work " + work + ". Took " + (work.getTimesTried() + 1) + "/" + _maxRetries + " attempts."); + logger.info("Completed work {}. Took {}/{} attempts.", work, work.getTimesTried() + 1, _maxRetries); work.setStep(Step.Done); } else { rescheduleWork(work, nextTime.longValue()); } } catch (Exception e) { - logger.warn("Encountered unhandled exception during HA process, reschedule work", e); + logger.warn("Encountered unhandled exception during HA process, reschedule work {}", work, e); long nextTime = getRescheduleTime(wt); rescheduleWork(work, nextTime); @@ -1085,11 +1127,11 @@ private void processWork(final HaWorkVO work) { } finally { if (!Step.Done.equals(work.getStep())) { if (work.getTimesTried() >= _maxRetries) { - logger.warn("Giving up, retried max " + work.getTimesTried() + "/" + _maxRetries + " times for work: " + work); + logger.warn("Giving up, retried max {}/{} times for work: {}", work.getTimesTried(), _maxRetries, work); work.setStep(Step.Done); } else { - logger.warn("Rescheduling work " + work + " to try again at " + new Date(work.getTimeToTry() << 10) + - ". Finished attempt " + work.getTimesTried() + "/" + _maxRetries + " times."); + logger.warn("Rescheduling work {} to try again at {}. Finished attempt {}/{} times.", + work, new Date(work.getTimeToTry() << 10), work.getTimesTried(), _maxRetries); } } _haDao.update(work.getId(), work); diff --git a/server/src/main/java/com/cloud/ha/KVMFencer.java b/server/src/main/java/com/cloud/ha/KVMFencer.java index b51ed00b028c..4a6606b09cc3 100644 --- a/server/src/main/java/com/cloud/ha/KVMFencer.java +++ b/server/src/main/java/com/cloud/ha/KVMFencer.java @@ -74,7 +74,7 @@ public KVMFencer() { @Override public Boolean fenceOff(VirtualMachine vm, Host host) { if (host.getHypervisorType() != HypervisorType.KVM && host.getHypervisorType() != HypervisorType.LXC) { - logger.warn("Don't know how to fence non kvm hosts " + host.getHypervisorType()); + logger.warn("Don't know how to fence non kvm hosts {}", host.getHypervisorType()); return null; } @@ -97,11 +97,8 @@ public Boolean fenceOff(VirtualMachine vm, Host host) { FenceAnswer answer; try { answer = (FenceAnswer)_agentMgr.send(h.getId(), fence); - } catch (AgentUnavailableException e) { - logger.info("Moving on to the next host because " + h.toString() + " is unavailable", e); - continue; - } catch (OperationTimedoutException e) { - logger.info("Moving on to the next host because " + h.toString() + " is unavailable", e); + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.info("Moving on to the next host because {} is unavailable", h.toString(), e); continue; } if (answer != null && answer.getResult()) { @@ -115,7 +112,7 @@ public Boolean fenceOff(VirtualMachine vm, Host host) { "Fencing off host " + host.getId() + " did not succeed after asking " + i + " hosts. " + "Check Agent logs for more information."); - logger.error("Unable to fence off " + vm.toString() + " on " + host.toString()); + logger.error("Unable to fence off {} on {}", vm.toString(), host.toString()); return false; } diff --git a/server/src/main/java/com/cloud/ha/ManagementIPSystemVMInvestigator.java b/server/src/main/java/com/cloud/ha/ManagementIPSystemVMInvestigator.java index 0972f2451afb..d14c4baafb34 100644 --- a/server/src/main/java/com/cloud/ha/ManagementIPSystemVMInvestigator.java +++ b/server/src/main/java/com/cloud/ha/ManagementIPSystemVMInvestigator.java @@ -104,7 +104,7 @@ public boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM { } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { return null; } diff --git a/server/src/main/java/com/cloud/ha/UserVmDomRInvestigator.java b/server/src/main/java/com/cloud/ha/UserVmDomRInvestigator.java index 7d063b3088e4..82074460f2af 100644 --- a/server/src/main/java/com/cloud/ha/UserVmDomRInvestigator.java +++ b/server/src/main/java/com/cloud/ha/UserVmDomRInvestigator.java @@ -103,7 +103,7 @@ public boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM { } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { if (logger.isDebugEnabled()) { logger.debug("checking if agent ({}) is alive", agent); } diff --git a/server/src/main/java/com/cloud/ha/XenServerInvestigator.java b/server/src/main/java/com/cloud/ha/XenServerInvestigator.java index 5482a7f148e6..fea44c97ab58 100644 --- a/server/src/main/java/com/cloud/ha/XenServerInvestigator.java +++ b/server/src/main/java/com/cloud/ha/XenServerInvestigator.java @@ -46,7 +46,7 @@ protected XenServerInvestigator() { } @Override - public Status isAgentAlive(Host agent) { + public Status getHostAgentStatus(Host agent) { if (agent.getHypervisorType() != HypervisorType.XenServer) { return null; } @@ -74,7 +74,7 @@ public Status isAgentAlive(Host agent) { @Override public boolean isVmAlive(VirtualMachine vm, Host host) throws UnknownVM { - Status status = isAgentAlive(host); + Status status = getHostAgentStatus(host); if (status == null) { throw new UnknownVM(); } diff --git a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java index 3303bc029333..6c1c3424b1b9 100644 --- a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java +++ b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java @@ -20,7 +20,7 @@ import com.cloud.agent.api.to.DataObjectType; import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; -import com.cloud.configuration.ConfigurationManagerImpl; +import com.cloud.capacity.CapacityManager; import com.cloud.event.EventTypes; import com.cloud.event.UsageEventUtils; import com.cloud.host.HostVO; @@ -44,6 +44,7 @@ import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.VMInstanceDao; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -54,9 +55,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.utils.bytescale.ByteScaleUtils; -import org.apache.commons.lang3.math.NumberUtils; public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru { @Inject @@ -130,30 +129,47 @@ protected double getVmSpeed(VirtualMachineTO to) { * @param vmProfile vm profile */ protected void setVmQuotaPercentage(VirtualMachineTO to, VirtualMachineProfile vmProfile) { - if (to.isLimitCpuUse()) { - VirtualMachine vm = vmProfile.getVirtualMachine(); - HostVO host = hostDao.findById(vm.getHostId()); - if (host == null) { - logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: {}", vm); - return; - } - logger.debug("Limiting CPU usage for VM: {} on host: {}", vm, host); - double hostMaxSpeed = getHostCPUSpeed(host); - double maxSpeed = getVmSpeed(to); - try { - BigDecimal percent = new BigDecimal(maxSpeed / hostMaxSpeed); - percent = percent.setScale(2, RoundingMode.HALF_DOWN); - if (percent.compareTo(new BigDecimal(1)) == 1) { - logger.debug("VM {} CPU MHz exceeded host {} CPU MHz, limiting VM CPU to the host maximum", vm, host); - percent = new BigDecimal(1); - } - to.setCpuQuotaPercentage(percent.doubleValue()); - logger.debug("Host: {} max CPU speed = {} MHz, VM: {} max CPU speed = {} MHz. " + - "Setting CPU quota percentage as: {}", - host, hostMaxSpeed, vm, maxSpeed, percent.doubleValue()); - } catch (NumberFormatException e) { - logger.error("Error calculating VM: {} quota percentage, it will not be set. Error: {}", vm, e.getMessage(), e); + if (!to.isLimitCpuUse()) { + return; + } + + VirtualMachine vm = vmProfile.getVirtualMachine(); + HostVO host = hostDao.findById(vm.getHostId()); + if (host == null) { + logger.warn("Host is not available. Skipping setting CPU quota percentage for VM: [{}].", vm); + return; + } + + logger.debug("Limiting CPU usage for VM: [{}] on host: [{}].", vm, host); + double maxSpeed = getVmSpeed(to); + double hostMaxSpeed = getHostCPUSpeed(host); + Double cpuQuotaPercentage = getCpuQuotaPercentage(maxSpeed, hostMaxSpeed); + if (cpuQuotaPercentage != null) { + to.setCpuQuotaPercentage(cpuQuotaPercentage); + } + } + + /** + * Calculates the VM quota percentage based on the VM and host CPU speeds. + * @param vmSpeeed Speed of the VM. + * @param hostSpeed Speed of the host. + * @return The VM quota percentage. + */ + public Double getCpuQuotaPercentage(double vmSpeeed, double hostSpeed) { + logger.debug("Calculating CPU quota percentage for VM with speed [{}] on host with speed [{}].", vmSpeeed, hostSpeed); + try { + BigDecimal percent = new BigDecimal(vmSpeeed / hostSpeed); + percent = percent.setScale(2, RoundingMode.HALF_DOWN); + if (percent.compareTo(new BigDecimal(1)) > 0) { + logger.debug("VM CPU speed exceeded host CPU speed and, therefore, limiting VM CPU quota to the host maximum."); + percent = new BigDecimal(1); } + double quotaPercentage = percent.doubleValue(); + logger.info("Calculated CPU quota percentage for VM with speed [{}] on host with speed [{}] is [{}].", vmSpeeed, hostSpeed, quotaPercentage); + return quotaPercentage; + } catch (NumberFormatException e) { + logger.info("Could not calculate CPU quota percentage for VM with speed [{}] on host with speed [{}]. Therefore, CPU limitation will not be set for the domain.", vmSpeeed, hostSpeed); + return null; } } @@ -214,28 +230,31 @@ protected void configureVmMemoryAndCpuCores(VirtualMachineTO virtualMachineTo, H Pair max = getHostMaxMemoryAndCpuCores(hostVo, virtualMachine, vmDescription); Long maxHostMemory = max.first(); - Integer maxHostCpuCore = max.second(); + Integer maxHostCpuCores = max.second(); long minMemory = virtualMachineTo.getMinRam(); Long maxMemory = virtualMachineTo.getMaxRam(); + long requestedMemory = maxMemory; + int minCpuCores = virtualMachineTo.getCpus(); - Integer maxCpuCores = minCpuCores; + int maxCpuCores = minCpuCores; - ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId()); - if (isVmDynamicScalable(serviceOfferingVO, virtualMachineTo, virtualMachine)) { + if (isVmDynamicScalable(virtualMachineTo, virtualMachine)) { + ServiceOfferingVO serviceOfferingVO = serviceOfferingDao.findById(virtualMachineProfile.getId(), virtualMachineProfile.getServiceOfferingId()); serviceOfferingDao.loadDetails(serviceOfferingVO); - maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory); - maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCore); + Long clusterId = hostVo != null ? hostVo.getClusterId() : null; + maxMemory = getVmMaxMemory(serviceOfferingVO, vmDescription, maxHostMemory, clusterId); + maxCpuCores = getVmMaxCpuCores(serviceOfferingVO, vmDescription, maxHostCpuCores, clusterId); } - virtualMachineTo.setRam(minMemory, maxMemory); + virtualMachineTo.setRam(minMemory, maxMemory, requestedMemory); virtualMachineTo.setCpus(minCpuCores); virtualMachineTo.setVcpuMaxLimit(maxCpuCores); } - protected boolean isVmDynamicScalable(ServiceOfferingVO serviceOfferingVO, VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) { - return serviceOfferingVO.isDynamic() && virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId()); + protected boolean isVmDynamicScalable(VirtualMachineTO virtualMachineTo, VirtualMachine virtualMachine) { + return virtualMachineTo.isEnableDynamicallyScaleVm() && UserVmManager.EnableDynamicallyScaleVm.valueIn(virtualMachine.getDataCenterId()); } protected Pair getHostMaxMemoryAndCpuCores(HostVO host, VirtualMachine virtualMachine, String vmDescription){ @@ -263,53 +282,34 @@ protected Pair getHostMaxMemoryAndCpuCores(HostVO host, VirtualMa return new Pair<>(maxHostMemory, maxHostCpuCore); } - protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory) { - String serviceOfferingDescription = serviceOfferingVO.toString(); - + protected Long getVmMaxMemory(ServiceOfferingVO serviceOfferingVO, String vmDescription, Long maxHostMemory, Long clusterId) { Long maxMemory; - Integer customOfferingMaxMemory = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_MEMORY)); - Integer maxMemoryConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.value(); - if (customOfferingMaxMemory != null) { - logger.debug(String.format("Using 'Custom unconstrained' %s max memory value [%sMb] as %s memory.", serviceOfferingDescription, customOfferingMaxMemory, vmDescription)); - maxMemory = ByteScaleUtils.mebibytesToBytes(customOfferingMaxMemory); + ConfigKey maxMemoryConfig = CapacityManager.KvmMemoryDynamicScalingCapacity; + Integer maxMemoryConfigValue = maxMemoryConfig.valueIn(clusterId); + logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] memory.", + serviceOfferingVO.toString(), maxMemoryConfig.key(), maxMemoryConfigValue, clusterId, vmDescription); + if (maxMemoryConfigValue > 0) { + maxMemory = ByteScaleUtils.mebibytesToBytes(maxMemoryConfigValue); } else { - String maxMemoryConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE.key(); - - logger.info(String.format("%s is a 'Custom unconstrained' service offering. Using config [%s] value [%s] as max %s memory.", - serviceOfferingDescription, maxMemoryConfigKey, maxMemoryConfig, vmDescription)); - - if (maxMemoryConfig > 0) { - maxMemory = ByteScaleUtils.mebibytesToBytes(maxMemoryConfig); - } else { - logger.info(String.format("Config [%s] has value less or equal '0'. Using %s host or last host max memory [%s] as VM max memory in the hypervisor.", maxMemoryConfigKey, vmDescription, maxHostMemory)); - maxMemory = maxHostMemory; - } + logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max memory [{}] as VM max memory in the hypervisor.", + maxMemoryConfig.key(), clusterId, vmDescription, maxHostMemory); + maxMemory = maxHostMemory; } return maxMemory; } - protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCore) { - String serviceOfferingDescription = serviceOfferingVO.toString(); - + protected Integer getVmMaxCpuCores(ServiceOfferingVO serviceOfferingVO, String vmDescription, Integer maxHostCpuCores, Long clusterId) { Integer maxCpuCores; - Integer customOfferingMaxCpuCores = NumberUtils.createInteger(serviceOfferingVO.getDetail(ApiConstants.MAX_CPU_NUMBER)); - Integer maxCpuCoresConfig = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.value(); - - if (customOfferingMaxCpuCores != null) { - logger.debug(String.format("Using 'Custom unconstrained' %s max cpu cores [%s] as %s cpu cores.", serviceOfferingDescription, customOfferingMaxCpuCores, vmDescription)); - maxCpuCores = customOfferingMaxCpuCores; + ConfigKey maxCpuCoresConfig = CapacityManager.KvmCpuDynamicScalingCapacity; + Integer maxCpuCoresConfigValue = maxCpuCoresConfig.valueIn(clusterId); + logger.info("[{}] is a dynamically scalable service offering. Using config [{}] value [{}] in cluster [ID: {}] as max [{}] CPU cores.", + serviceOfferingVO.toString(), maxCpuCoresConfig.key(), maxCpuCoresConfigValue, clusterId, vmDescription); + if (maxCpuCoresConfigValue > 0) { + maxCpuCores = maxCpuCoresConfigValue; } else { - String maxCpuCoreConfigKey = ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES.key(); - - logger.info(String.format("%s is a 'Custom unconstrained' service offering. Using config [%s] value [%s] as max %s cpu cores.", - serviceOfferingDescription, maxCpuCoreConfigKey, maxCpuCoresConfig, vmDescription)); - - if (maxCpuCoresConfig > 0) { - maxCpuCores = maxCpuCoresConfig; - } else { - logger.info(String.format("Config [%s] has value less or equal '0'. Using %s host or last host max cpu cores [%s] as VM cpu cores in the hypervisor.", maxCpuCoreConfigKey, vmDescription, maxHostCpuCore)); - maxCpuCores = maxHostCpuCore; - } + logger.info("Config [{}] in cluster [ID: {}] has value less or equal '0'. Using [{}] host or last host max CPU cores [{}] as VM CPU cores in the hypervisor.", + maxCpuCoresConfig.key(), clusterId, vmDescription, maxHostCpuCores); + maxCpuCores = maxHostCpuCores; } return maxCpuCores; } diff --git a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java index efda72555e2c..b9fa3f0ebae7 100644 --- a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java +++ b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java @@ -21,7 +21,6 @@ import java.net.InetAddress; import java.net.URI; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -32,11 +31,8 @@ import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.ca.CAManager; -import org.apache.cloudstack.ca.SetupCertificateCommand; import org.apache.cloudstack.direct.download.DirectDownloadManager; -import org.apache.cloudstack.framework.ca.Certificate; import org.apache.cloudstack.utils.cache.LazyCache; -import org.apache.cloudstack.utils.security.KeyStoreUtils; import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; @@ -66,7 +62,6 @@ import com.cloud.resource.ResourceStateAdapter; import com.cloud.resource.ServerResource; import com.cloud.resource.UnableDeleteHostException; -import com.cloud.utils.PasswordGenerator; import com.cloud.utils.StringUtils; import com.cloud.utils.UuidUtils; import com.cloud.utils.exception.CloudRuntimeException; @@ -174,55 +169,7 @@ private void setupAgentSecurity(final Connection sshConnection, final String age throw new CloudRuntimeException("Cannot secure agent communication because SSH connection is invalid for host IP=" + agentIp); } - Integer validityPeriod = CAManager.CertValidityPeriod.value(); - if (validityPeriod < 1) { - validityPeriod = 1; - } - - String keystorePassword = PasswordGenerator.generateRandomPassword(16); - final SSHCmdHelper.SSHCmdResult keystoreSetupResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection, - String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + - "/etc/cloudstack/agent/agent.properties " + - "/etc/cloudstack/agent/%s " + - "%s %d " + - "/etc/cloudstack/agent/%s", - KeyStoreUtils.KS_SETUP_SCRIPT, - KeyStoreUtils.KS_FILENAME, - keystorePassword, - validityPeriod, - KeyStoreUtils.CSR_FILENAME)); - - if (!keystoreSetupResult.isSuccess()) { - throw new CloudRuntimeException("Failed to setup keystore on the KVM host: " + agentIp); - } - - final Certificate certificate = caManager.issueCertificate(keystoreSetupResult.getStdOut(), Arrays.asList(agentHostname, agentIp), Collections.singletonList(agentIp), null, null); - if (certificate == null || certificate.getClientCertificate() == null) { - throw new CloudRuntimeException("Failed to issue certificates for KVM host agent: " + agentIp); - } - - final SetupCertificateCommand certificateCommand = new SetupCertificateCommand(certificate); - final SSHCmdHelper.SSHCmdResult setupCertResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection, - String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + - "/etc/cloudstack/agent/agent.properties %s " + - "/etc/cloudstack/agent/%s %s " + - "/etc/cloudstack/agent/%s \"%s\" " + - "/etc/cloudstack/agent/%s \"%s\" " + - "/etc/cloudstack/agent/%s \"%s\"", - KeyStoreUtils.KS_IMPORT_SCRIPT, - keystorePassword, - KeyStoreUtils.KS_FILENAME, - KeyStoreUtils.SSH_MODE, - KeyStoreUtils.CERT_FILENAME, - certificateCommand.getEncodedCertificate(), - KeyStoreUtils.CACERT_FILENAME, - certificateCommand.getEncodedCaCertificates(), - KeyStoreUtils.PKEY_FILENAME, - certificateCommand.getEncodedPrivateKey())); - - if (setupCertResult != null && !setupCertResult.isSuccess()) { - throw new CloudRuntimeException("Failed to setup certificate in the KVM agent's keystore file, please see logs and configure manually!"); - } + caManager.provisionCertificateViaSsh(sshConnection, agentIp, agentHostname, null); if (logger.isDebugEnabled()) { logger.debug("Succeeded to import certificate in the keystore for agent on the KVM host: " + agentIp + ". Agent secured and trusted."); diff --git a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java index 7f41a1a106cb..da84e5058603 100644 --- a/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java +++ b/server/src/main/java/com/cloud/network/IpAddressManagerImpl.java @@ -539,6 +539,9 @@ public boolean configure(String name, Map params) { AssignIpAddressSearch.and("allocated", AssignIpAddressSearch.entity().getAllocatedTime(), Op.NULL); AssignIpAddressSearch.and("vlanId", AssignIpAddressSearch.entity().getVlanId(), Op.IN); AssignIpAddressSearch.and("forSystemVms", AssignIpAddressSearch.entity().isForSystemVms(), Op.EQ); + AssignIpAddressSearch.and("id", AssignIpAddressSearch.entity().getId(), Op.NIN); + AssignIpAddressSearch.and("requestedAddress", AssignIpAddressSearch.entity().getAddress(), Op.EQ); + AssignIpAddressSearch.and("routerAddress", AssignIpAddressSearch.entity().getAddress(), Op.NEQ); SearchBuilder vlanSearch = _vlanDao.createSearchBuilder(); vlanSearch.and("type", vlanSearch.entity().getVlanType(), Op.EQ); @@ -945,10 +948,23 @@ public List listAvailablePublicIps(final long dcId, final Long podI if (podId != null) { sc = AssignIpAddressFromPodVlanSearch.create(); sc.setJoinParameters("podVlanMapSB", "podId", podId); - errorMessage.append(" pod id=" + podId); + errorMessage.append(" pod id=").append(podId); } else { sc = AssignIpAddressSearch.create(); - errorMessage.append(" zone id=" + dcId); + errorMessage.append(" zone id=").append(dcId); + } + + if (lockOneRow) { + logger.debug("Listing quarantined public IPs to ignore on search for public IP for system VM. The IPs ignored will be the ones that: were not associated to account [{}]; were not removed yet; and with quarantine end dates after [{}].", owner.getUuid(), new Date()); + + List quarantinedAddresses = publicIpQuarantineDao.listQuarantinedIpAddressesToUser(owner.getId(), new Date()); + List quarantinedAddressesIDs = quarantinedAddresses.stream().map(PublicIpQuarantineVO::getPublicIpAddressId).collect(Collectors.toList()); + + logger.debug("Found addresses with the following IDs: [{}] that will be ignored when searching for available public IPs.", quarantinedAddressesIDs); + + if (CollectionUtils.isNotEmpty(quarantinedAddressesIDs)) { + sc.setParameters("id", quarantinedAddressesIDs.toArray()); + } } sc.setParameters("dc", dcId); @@ -956,11 +972,11 @@ public List listAvailablePublicIps(final long dcId, final Long podI // for direct network take ip addresses only from the vlans belonging to the network if (vlanUse == VlanType.DirectAttached) { sc.setJoinParameters("vlan", "networkId", guestNetworkId); - errorMessage.append(", network id=" + guestNetworkId); + errorMessage.append(", network id=").append(guestNetworkId); } if (requestedGateway != null) { sc.setJoinParameters("vlan", "vlanGateway", requestedGateway); - errorMessage.append(", requested gateway=" + requestedGateway); + errorMessage.append(", requested gateway=").append(requestedGateway); } sc.setJoinParameters("vlan", "type", vlanUse); @@ -970,38 +986,39 @@ public List listAvailablePublicIps(final long dcId, final Long podI NetworkDetailVO routerIpDetail = _networkDetailsDao.findDetail(network.getId(), ApiConstants.ROUTER_IP); routerIpAddress = routerIpDetail != null ? routerIpDetail.getValue() : null; } + if (requestedIp != null) { - sc.addAnd("address", SearchCriteria.Op.EQ, requestedIp); - errorMessage.append(": requested ip " + requestedIp + " is not available"); + sc.setParameters("requestedAddress", requestedIp); + errorMessage.append(": requested ip ").append(requestedIp).append(" is not available"); } else if (routerIpAddress != null) { - sc.addAnd("address", Op.NEQ, routerIpAddress); + sc.setParameters("routerAddress", routerIpAddress); } boolean ascOrder = ! forSystemVms; - Filter filter = new Filter(IPAddressVO.class, "forSystemVms", ascOrder, 0l, 1l); + Filter filter = new Filter(IPAddressVO.class, "forSystemVms", ascOrder, 0L, 1L); filter.addOrderBy(IPAddressVO.class,"vlanId", true); - List addrs = new ArrayList<>(); + List addresses = new ArrayList<>(); if (forSystemVms) { // Get Public IPs for system vms in dedicated ranges sc.setParameters("forSystemVms", true); if (lockOneRow) { - addrs = _ipAddressDao.lockRows(sc, filter, true); + addresses = _ipAddressDao.lockRows(sc, filter, true); } else { - addrs = new ArrayList<>(_ipAddressDao.search(sc, null)); + addresses = new ArrayList<>(_ipAddressDao.search(sc, null)); } } - if ((!lockOneRow || (lockOneRow && CollectionUtils.isEmpty(addrs))) && + if ((!lockOneRow || (lockOneRow && CollectionUtils.isEmpty(addresses))) && !(forSystemVms && SystemVmPublicIpReservationModeStrictness.value())) { sc.setParameters("forSystemVms", false); // If owner has dedicated Public IP ranges, fetch IP from the dedicated range // Otherwise fetch IP from the system pool // Checking if network is null in the case of system VM's. At the time of allocation of IP address to systemVm, no network is present. if (network == null || !(network.getGuestType() == GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) { - List maps = _accountVlanMapDao.listAccountVlanMapsByAccount(owner.getId()); - for (AccountVlanMapVO map : maps) { + List accountVlanMaps = _accountVlanMapDao.listAccountVlanMapsByAccount(owner.getId()); + for (AccountVlanMapVO map : accountVlanMaps) { if (vlanDbIds == null || vlanDbIds.contains(map.getVlanDbId())) dedicatedVlanDbIds.add(map.getVlanDbId()); } @@ -1020,10 +1037,10 @@ public List listAvailablePublicIps(final long dcId, final Long podI if (!dedicatedVlanDbIds.isEmpty()) { fetchFromDedicatedRange = true; sc.setParameters("vlanId", dedicatedVlanDbIds.toArray()); - errorMessage.append(", vlanId id=" + Arrays.toString(dedicatedVlanDbIds.toArray())); + errorMessage.append(", vlanId id=").append(Arrays.toString(dedicatedVlanDbIds.toArray())); } else if (!nonDedicatedVlanDbIds.isEmpty()) { sc.setParameters("vlanId", nonDedicatedVlanDbIds.toArray()); - errorMessage.append(", vlanId id=" + Arrays.toString(nonDedicatedVlanDbIds.toArray())); + errorMessage.append(", vlanId id=").append(Arrays.toString(nonDedicatedVlanDbIds.toArray())); } else { if (podId != null) { InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", Pod.class, podId); @@ -1037,13 +1054,13 @@ public List listAvailablePublicIps(final long dcId, final Long podI } } if (lockOneRow) { - addrs = _ipAddressDao.lockRows(sc, filter, true); + addresses = _ipAddressDao.lockRows(sc, filter, true); } else { - addrs = new ArrayList<>(_ipAddressDao.search(sc, null)); + addresses = new ArrayList<>(_ipAddressDao.search(sc, null)); } // If all the dedicated IPs of the owner are in use fetch an IP from the system pool - if ((!lockOneRow || (lockOneRow && addrs.size() == 0)) && fetchFromDedicatedRange && vlanUse == VlanType.VirtualNetwork) { + if ((!lockOneRow || (lockOneRow && addresses.isEmpty())) && fetchFromDedicatedRange && vlanUse == VlanType.VirtualNetwork) { // Verify if account is allowed to acquire IPs from the system boolean useSystemIps = UseSystemPublicIps.valueIn(owner.getId()); if (useSystemIps && !nonDedicatedVlanDbIds.isEmpty()) { @@ -1051,15 +1068,15 @@ public List listAvailablePublicIps(final long dcId, final Long podI sc.setParameters("vlanId", nonDedicatedVlanDbIds.toArray()); errorMessage.append(", vlanId id=" + Arrays.toString(nonDedicatedVlanDbIds.toArray())); if (lockOneRow) { - addrs = _ipAddressDao.lockRows(sc, filter, true); + addresses = _ipAddressDao.lockRows(sc, filter, true); } else { - addrs.addAll(_ipAddressDao.search(sc, null)); + addresses.addAll(_ipAddressDao.search(sc, null)); } } } } - if (lockOneRow && addrs.size() == 0) { + if (lockOneRow && addresses.isEmpty()) { if (podId != null) { InsufficientAddressCapacityException ex = new InsufficientAddressCapacityException("Insufficient address capacity", Pod.class, podId); // for now, we hardcode the table names, but we should ideally do a lookup for the tablename from the VO object. @@ -1073,13 +1090,12 @@ public List listAvailablePublicIps(final long dcId, final Long podI } if (lockOneRow) { - assert (addrs.size() == 1) : "Return size is incorrect: " + addrs.size(); - IpAddress ipAddress = addrs.get(0); - boolean ipCanBeAllocated = canPublicIpAddressBeAllocated(ipAddress, owner); + IPAddressVO allocatableIp = addresses.get(0); + + boolean isPublicIpAllocatable = canPublicIpAddressBeAllocated(allocatableIp, owner); - if (!ipCanBeAllocated) { - throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP address [%s] as it is in quarantine.", ipAddress.getAddress()), - DataCenter.class, dcId); + if (!isPublicIpAllocatable) { + throw new InsufficientAddressCapacityException(String.format("Failed to allocate public IP [%s] as it is in quarantine.", allocatableIp.getAddress()), DataCenter.class, dcId); } } @@ -1088,12 +1104,12 @@ public List listAvailablePublicIps(final long dcId, final Long podI try { _resourceLimitMgr.checkResourceLimit(owner, ResourceType.public_ip); } catch (ResourceAllocationException ex) { - logger.warn("Failed to allocate resource of type " + ex.getResourceType() + " for account " + owner); + logger.warn("Failed to allocate resource of type {} for account {}", ex.getResourceType(), owner); throw new AccountLimitException("Maximum number of public IP addresses for account: " + owner.getAccountName() + " has been exceeded."); } } - return addrs; + return addresses; } @DB @@ -2558,26 +2574,27 @@ public boolean canPublicIpAddressBeAllocated(IpAddress ip, Account newOwner) { PublicIpQuarantineVO publicIpQuarantineVO = publicIpQuarantineDao.findByPublicIpAddressId(ip.getId()); if (publicIpQuarantineVO == null) { - logger.debug(String.format("Public IP address [%s] is not in quarantine; therefore, it is allowed to be allocated.", ip)); + logger.debug("Public IP address [{}] is not in quarantine; therefore, it is allowed to be allocated.", ip); return true; } if (!isPublicIpAddressStillInQuarantine(publicIpQuarantineVO, new Date())) { - logger.debug(String.format("Public IP address [%s] is no longer in quarantine; therefore, it is allowed to be allocated.", ip)); + logger.debug("Public IP address [{}] is no longer in quarantine; therefore, it is allowed to be allocated.", ip); + removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(), "IP was removed from quarantine because it was no longer in quarantine."); return true; } Account previousOwner = _accountMgr.getAccount(publicIpQuarantineVO.getPreviousOwnerId()); if (Objects.equals(previousOwner.getUuid(), newOwner.getUuid())) { - logger.debug(String.format("Public IP address [%s] is in quarantine; however, the Public IP previous owner [%s] is the same as the new owner [%s]; therefore the IP" + - " can be allocated. The public IP address will be removed from quarantine.", ip, previousOwner, newOwner)); + logger.debug("Public IP address [{}] is in quarantine; however, the Public IP previous owner [{}] is the same as the new owner [{}]; therefore the IP" + + " can be allocated. The public IP address will be removed from quarantine.", ip, previousOwner, newOwner); removePublicIpAddressFromQuarantine(publicIpQuarantineVO.getId(), "IP was removed from quarantine because it has been allocated by the previous owner"); return true; } - logger.error(String.format("Public IP address [%s] is in quarantine and the previous owner [%s] is different than the new owner [%s]; therefore, the IP cannot be " + - "allocated.", ip, previousOwner, newOwner)); + logger.error("Public IP address [{}] is in quarantine and the previous owner [{}] is different than the new owner [{}]; therefore, the IP cannot be " + + "allocated.", ip, previousOwner, newOwner); return false; } @@ -2628,7 +2645,7 @@ public void removePublicIpAddressFromQuarantine(Long quarantineProcessId, String publicIpQuarantineVO.setRemovalReason(removalReason); publicIpQuarantineVO.setRemoverAccountId(removerAccountId); - logger.debug(String.format("Removing public IP Address [%s] from quarantine by updating the removed date to [%s].", ipAddress, removedDate)); + logger.debug("Removing public IP Address [{}] from quarantine by updating the removed date to [{}].", ipAddress, removedDate); publicIpQuarantineDao.persist(publicIpQuarantineVO); } diff --git a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java index 450f08c46e35..a09867b8ffc3 100644 --- a/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkMigrationManagerImpl.java @@ -301,7 +301,7 @@ public Long makeCopyOfVpc(long vpcId, long vpcOfferingId) { copyOfVpc = _vpcService.createVpc(vpc.getZoneId(), vpcOfferingId, vpc.getAccountId(), vpc.getName(), vpc.getDisplayText(), vpc.getCidr(), vpc.getNetworkDomain(), vpc.getIp4Dns1(), vpc.getIp4Dns2(), - vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu(), null, null, null, vpc.useRouterIpAsResolver()); + vpc.getIp6Dns1(), vpc.getIp6Dns2(), vpc.isDisplay(), vpc.getPublicMtu(), null, null, null, vpc.useRouterIpAsResolver(), vpc.getKeepMacAddressOnPublicNic()); copyOfVpcId = copyOfVpc.getId(); //on resume of migration the uuid will be swapped already. So the copy will have the value of the original vpcid. diff --git a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java index c9884f8c469a..d18fd043f697 100644 --- a/server/src/main/java/com/cloud/network/NetworkServiceImpl.java +++ b/server/src/main/java/com/cloud/network/NetworkServiceImpl.java @@ -1549,6 +1549,8 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac DataCenter zone = getAndValidateZone(cmd, pNtwk); + boolean keepMacAddressOnPublicNic = getAndValidateSupportForKeepMacAddressOnPublicNicParameter(cmd.getKeepMacAddressOnPublicNic(), ntwkOff); + _accountMgr.checkAccess(owner, ntwkOff, zone); validateZoneAvailability(caller, zone); @@ -1834,7 +1836,7 @@ public Network createGuestNetwork(CreateNetworkCmd cmd) throws InsufficientCapac Network network = commitNetwork(networkOfferingId, gateway, startIP, endIP, netmask, networkDomain, vlanId, bypassVlanOverlapCheck, name, displayText, caller, physicalNetworkId, zone.getId(), domainId, isDomainSpecific, subdomainAccess, vpcId, startIPv6, endIPv6, ip6Gateway, ip6Cidr, displayNetwork, aclId, secondaryVlanId, privateVlanType, ntwkOff, pNtwk, aclType, owner, cidr, createVlan, - externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize); + externalId, routerIPv4, routerIPv6, associatedNetwork, ip4Dns1, ip4Dns2, ip6Dns1, ip6Dns2, interfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); // retrieve, acquire and associate the correct IP addresses checkAndSetRouterSourceNatIp(owner, cmd, network); @@ -1879,6 +1881,24 @@ private void validateNetworkCreationSupported(long zoneId, String zoneName, Gues } } + protected boolean getAndValidateSupportForKeepMacAddressOnPublicNicParameter(Boolean keepMacAddressOnPublicNic, NetworkOffering networkOffering) { + if (networkOffering.isForVpc() && keepMacAddressOnPublicNic != null) { + throw new InvalidParameterValueException( + String.format("The [%s] parameter cannot be specified on the creation of VPC tiers.", ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC) + ); + } + + GuestType guestType = networkOffering.getGuestType(); + if (guestType != GuestType.Isolated && keepMacAddressOnPublicNic != null) { + throw new InvalidParameterValueException(String.format( + "The [%s] parameter can only be specified on the creation of [%s] networks.", + ApiConstants.KEEP_MAC_ADDRESS_ON_PUBLIC_NIC, GuestType.Isolated + )); + } + + return keepMacAddressOnPublicNic == null || keepMacAddressOnPublicNic; + } + @Override @DB @ActionEvent(eventType = EventTypes.EVENT_NETWORK_CREATE, eventDescription = "creating network") @@ -2282,7 +2302,7 @@ protected Network commitNetwork(final Long networkOfferingId, final String gatew final Boolean displayNetwork, final Long aclId, final String isolatedPvlan, final PVlanType isolatedPvlanType, final NetworkOffering ntwkOff, final PhysicalNetwork pNtwk, final ACLType aclType, final Account ownerFinal, final String cidr, final boolean createVlan, final String externalId, String routerIp, String routerIpv6, final Network associatedNetwork, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, Pair vrIfaceMTUs, - final Integer networkCidrSize) throws InsufficientCapacityException, ResourceAllocationException { + final Integer networkCidrSize, final boolean keepMacAddressOnPublicNic) throws InsufficientCapacityException, ResourceAllocationException { try { Network network = Transaction.execute(new TransactionCallbackWithException() { @Override @@ -2348,7 +2368,7 @@ public Network doInTransaction(TransactionStatus status) throws InsufficientCapa } network = _networkMgr.createGuestNetwork(networkOfferingId, name, displayText, gateway, cidr, vlanId, bypassVlanOverlapCheck, networkDomain, owner, sharedDomainId, pNtwk, zoneId, aclType, subdomainAccess, vpcId, ip6Gateway, ip6Cidr, displayNetwork, isolatedPvlan, isolatedPvlanType, externalId, routerIp, routerIpv6, ip4Dns1, ip4Dns2, - ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize); + ip6Dns1, ip6Dns2, vrIfaceMTUs, networkCidrSize, keepMacAddressOnPublicNic); } if (createVlan && network != null) { @@ -3156,6 +3176,7 @@ public Network updateGuestNetwork(final UpdateNetworkCmd cmd) { String ip4Dns2 = cmd.getIp4Dns2(); String ip6Dns1 = cmd.getIp6Dns1(); String ip6Dns2 = cmd.getIp6Dns2(); + Boolean keepMacAddressOnPublicNic = cmd.getKeepMacAddressOnPublicNic(); boolean restartNetwork = false; @@ -3214,6 +3235,10 @@ public Network updateGuestNetwork(final UpdateNetworkCmd cmd) { network.setUuid(customId); } + if (keepMacAddressOnPublicNic != null) { + network.setKeepMacAddressOnPublicNic(getAndValidateSupportForKeepMacAddressOnPublicNicParameter(keepMacAddressOnPublicNic, offering)); + } + // display flag is not null and has changed if (displayNetwork != null && displayNetwork != network.getDisplayNetwork()) { // Update resource count if it needs to be updated diff --git a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java index 684f196ce9fd..56534a8ee7df 100644 --- a/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java +++ b/server/src/main/java/com/cloud/network/router/NetworkHelperImpl.java @@ -737,55 +737,91 @@ protected LinkedHashMap> configureControlNic } protected LinkedHashMap> configurePublicNic(final RouterDeploymentDefinition routerDeploymentDefinition, final boolean hasGuestNic) throws InsufficientAddressCapacityException { - final LinkedHashMap> publicConfig = new LinkedHashMap>(3); - - if (routerDeploymentDefinition.isPublicNetwork()) { - logger.debug("Adding NIC for Virtual Router in Public network "); - // if source nat service is supported by the network, get the source - // nat ip address - final NicProfile defaultNic = new NicProfile(); - defaultNic.setDefaultNic(true); - final PublicIp sourceNatIp = routerDeploymentDefinition.getSourceNatIP(); - defaultNic.setIPv4Address(sourceNatIp.getAddress().addr()); - defaultNic.setIPv4Gateway(sourceNatIp.getGateway()); - defaultNic.setIPv4Netmask(sourceNatIp.getNetmask()); - defaultNic.setMacAddress(sourceNatIp.getMacAddress()); - // get broadcast from public network - final Network pubNet = _networkDao.findById(sourceNatIp.getNetworkId()); - if (pubNet.getBroadcastDomainType() == BroadcastDomainType.Vxlan) { - defaultNic.setBroadcastType(BroadcastDomainType.Vxlan); - defaultNic.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); - defaultNic.setIsolationUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); - } else { - defaultNic.setBroadcastType(BroadcastDomainType.Vlan); - defaultNic.setBroadcastUri(sourceNatIp.getVlanTag() != null ? BroadcastDomainType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); - defaultNic.setIsolationUri(sourceNatIp.getVlanTag() != null ? IsolationType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); - } - - //If guest nic has already been added we will have 2 devices in the list. - if (hasGuestNic) { - defaultNic.setDeviceId(2); - } + final LinkedHashMap> publicConfig = new LinkedHashMap<>(3); + if (!routerDeploymentDefinition.isPublicNetwork()) { + return publicConfig; + } - final NetworkOffering publicOffering = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemPublicNetwork).get(0); - final List publicNetworks = _networkMgr.setupNetwork(s_systemAccount, publicOffering, routerDeploymentDefinition.getPlan(), null, null, false); - final String publicIp = defaultNic.getIPv4Address(); - // We want to use the identical MAC address for RvR on public - // interface if possible - final NicVO peerNic = _nicDao.findByIp4AddressAndNetworkId(publicIp, publicNetworks.get(0).getId()); - if (peerNic != null) { - logger.info("Use same MAC as previous RvR, the MAC is " + peerNic.getMacAddress()); - defaultNic.setMacAddress(peerNic.getMacAddress()); - } - if (routerDeploymentDefinition.getGuestNetwork() != null) { - ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); - } - publicConfig.put(publicNetworks.get(0), new ArrayList(Arrays.asList(defaultNic))); + final PublicIp sourceNatIp = routerDeploymentDefinition.getSourceNatIP(); + final NicProfile defaultNic = new NicProfile(); + configurePublicVrNicBasedOnSourceNatIp(defaultNic, sourceNatIp); + if (hasGuestNic) { + logger.debug("Guest NIC has already been configured, therefore setting device ID of new VR (with source NAT [{}]) public NIC to [2].", sourceNatIp); + defaultNic.setDeviceId(2); } + final NetworkOffering publicOffering = _networkModel.getSystemAccountNetworkOfferings(NetworkOffering.SystemPublicNetwork).get(0); + final List publicNetworks = _networkMgr.setupNetwork(s_systemAccount, publicOffering, routerDeploymentDefinition.getPlan(), null, null, false); + + setPublicNicMacAddressSameAsPeerNic(defaultNic, publicNetworks.get(0), routerDeploymentDefinition); + + if (routerDeploymentDefinition.getGuestNetwork() != null) { + ipv6Service.updateNicIpv6(defaultNic, routerDeploymentDefinition.getDest().getDataCenter(), routerDeploymentDefinition.getGuestNetwork()); + } + publicConfig.put(publicNetworks.get(0), List.of(defaultNic)); return publicConfig; } + /** + * Configures the public NIC of a Virtual Router based on the provided source NAT IP. + * @param nic Virtual Router public NIC to be configured. + * @param sourceNatIp Source NAT IP which should be used to configure the Virtual Router's public NIC. + */ + protected void configurePublicVrNicBasedOnSourceNatIp(NicProfile nic, PublicIp sourceNatIp) { + logger.debug("Configuring public NIC of VR with source NAT IP equal to [{}]", sourceNatIp.getAddress().addr()); + nic.setDefaultNic(true); + nic.setIPv4Address(sourceNatIp.getAddress().addr()); + nic.setIPv4Gateway(sourceNatIp.getGateway()); + nic.setIPv4Netmask(sourceNatIp.getNetmask()); + nic.setMacAddress(sourceNatIp.getMacAddress()); + + Network publicNetwork = _networkDao.findById(sourceNatIp.getNetworkId()); + if (publicNetwork.getBroadcastDomainType() == BroadcastDomainType.Vxlan) { + nic.setBroadcastType(BroadcastDomainType.Vxlan); + nic.setBroadcastUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); + nic.setIsolationUri(BroadcastDomainType.Vxlan.toUri(sourceNatIp.getVlanTag())); + } else { + nic.setBroadcastType(BroadcastDomainType.Vlan); + nic.setBroadcastUri(sourceNatIp.getVlanTag() != null ? BroadcastDomainType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); + nic.setIsolationUri(sourceNatIp.getVlanTag() != null ? IsolationType.Vlan.toUri(sourceNatIp.getVlanTag()) : null); + } + } + + /** + * Sets the MAC address of the new VR's public NIC the same as the previous VR's public NIC MAC address if + * {@link RouterDeploymentDefinition#getKeepMacAddressOnPublicNic()} is set to {@code true} and a peer NIC is found; otherwise, + * does nothing. + */ + protected void setPublicNicMacAddressSameAsPeerNic(NicProfile defaultNic, Network publicNetwork, RouterDeploymentDefinition routerDeploymentDefinition) throws InsufficientAddressCapacityException { + String publicIp = defaultNic.getIPv4Address(); + logger.info("Verifying if we will use same MAC address for public NIC of new VR (with source NAT [{}]).", publicIp); + + logger.debug("Searching for peer NIC for public IP [{}] and network [{}].", publicIp, publicNetwork.getUuid()); + NicVO peerNic = _nicDao.findByIp4AddressAndNetworkId(publicIp, publicNetwork.getId()); + if (peerNic == null) { + logger.info("We will not use the same MAC address for public NIC of new VR as we have not found a peer NIC for public IP [{}] and network [{}].", + publicIp, publicNetwork.getUuid()); + return; + } + + logger.info("Found peer NIC [{}] for public IP [{}] and network [{}].", peerNic.getUuid(), publicIp, publicNetwork.getUuid()); + boolean keepMacAddressOnPublicNic = routerDeploymentDefinition.getKeepMacAddressOnPublicNic(); + String macAddressLog = String.format("same MAC address for public NIC of new VR (with source NAT [%s]) as the [keep_mac_address_on_public_nic] property is configured as [%s].", publicIp, keepMacAddressOnPublicNic); + if (keepMacAddressOnPublicNic) { + logger.info("Using {}", macAddressLog); + defaultNic.setMacAddress(peerNic.getMacAddress()); + return; + } + + logger.info("Not using {}", macAddressLog); + boolean fetchNewMacAddress = peerNic.getMacAddress().equals(defaultNic.getMacAddress()); + if (fetchNewMacAddress) { + logger.debug("Fetching new MAC address for public NIC of new VR, since the MAC address of the peer NIC [UUID: {}, MAC address: {}] is equal to the predefined MAC address of the current NIC.", peerNic.getUuid(), peerNic.getMacAddress()); + routerDeploymentDefinition.findSourceNatIP(); + configurePublicVrNicBasedOnSourceNatIp(defaultNic, routerDeploymentDefinition.getSourceNatIP()); + } + } + @Override public LinkedHashMap> configureDefaultNics(final RouterDeploymentDefinition routerDeploymentDefinition) throws ConcurrentOperationException, InsufficientAddressCapacityException { diff --git a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java index 5b4ab908ca4c..c5f36e856550 100644 --- a/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java +++ b/server/src/main/java/com/cloud/network/vpc/VpcManagerImpl.java @@ -1568,7 +1568,7 @@ public List getVpcOfferingZones(Long vpcOfferingId) { @ActionEvent(eventType = EventTypes.EVENT_VPC_CREATE, eventDescription = "creating vpc", create = true) public Vpc createVpc(final long zoneId, final long vpcOffId, final long vpcOwnerId, final String vpcName, final String displayText, final String cidr, String networkDomain, final String ip4Dns1, final String ip4Dns2, final String ip6Dns1, final String ip6Dns2, final Boolean displayVpc, Integer publicMtu, - final Integer cidrSize, final Long asNumber, final List bgpPeerIds, Boolean useVrIpResolver) throws ResourceAllocationException { + final Integer cidrSize, final Long asNumber, final List bgpPeerIds, Boolean useVrIpResolver, boolean keepMacAddressOnPublicNic) throws ResourceAllocationException { final Account caller = CallContext.current().getCallingAccount(); final Account owner = _accountMgr.getAccount(vpcOwnerId); @@ -1668,6 +1668,7 @@ public Vpc createVpc(final long zoneId, final long vpcOffId, final long vpcOwner vpc.setPublicMtu(publicMtu); vpc.setDisplay(Boolean.TRUE.equals(displayVpc)); vpc.setUseRouterIpResolver(Boolean.TRUE.equals(useVrIpResolver)); + vpc.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); try (CheckedReservation vpcReservation = new CheckedReservation(owner, ResourceType.vpc, null, null, 1L, reservationDao, _resourceLimitMgr)) { if (vpc.getCidr() == null && cidrSize != null) { @@ -1728,7 +1729,8 @@ public Vpc createVpc(CreateVPCCmd cmd) throws ResourceAllocationException { List bgpPeerIds = (cmd instanceof CreateVPCCmdByAdmin) ? ((CreateVPCCmdByAdmin)cmd).getBgpPeerIds() : null; Vpc vpc = createVpc(cmd.getZoneId(), cmd.getVpcOffering(), cmd.getEntityOwnerId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCidr(), cmd.getNetworkDomain(), cmd.getIp4Dns1(), cmd.getIp4Dns2(), cmd.getIp6Dns1(), - cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu(), cmd.getCidrSize(), cmd.getAsNumber(), bgpPeerIds, cmd.getUseVrIpResolver()); + cmd.getIp6Dns2(), cmd.isDisplay(), cmd.getPublicMtu(), cmd.getCidrSize(), cmd.getAsNumber(), bgpPeerIds, + cmd.getUseVrIpResolver(), cmd.getKeepMacAddressOnPublicNic()); String sourceNatIP = cmd.getSourceNatIP(); boolean forNsx = isVpcForProvider(Provider.Nsx, vpc); @@ -1940,12 +1942,14 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { @Override public Vpc updateVpc(UpdateVPCCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException { - return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP()); + return updateVpc(cmd.getId(), cmd.getVpcName(), cmd.getDisplayText(), cmd.getCustomId(), + cmd.isDisplayVpc(), cmd.getPublicMtu(), cmd.getSourceNatIP(), cmd.getKeepMacAddressOnPublicNic()); } @Override @ActionEvent(eventType = EventTypes.EVENT_VPC_UPDATE, eventDescription = "updating vpc") - public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, final Boolean displayVpc, Integer mtu, String sourceNatIp) throws ResourceUnavailableException, InsufficientCapacityException { + public Vpc updateVpc(final long vpcId, final String vpcName, final String displayText, final String customId, + final Boolean displayVpc, Integer mtu, String sourceNatIp, Boolean keepMacAddressOnPublicNic) throws ResourceUnavailableException, InsufficientCapacityException { final Account caller = CallContext.current().getCallingAccount(); // Verify input parameters @@ -1977,6 +1981,10 @@ public Vpc updateVpc(final long vpcId, final String vpcName, final String displa vpc.setDisplay(displayVpc); } + if (keepMacAddressOnPublicNic != null) { + vpc.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); + } + mtu = validateMtu(vpcToUpdate, mtu); if (mtu != null) { updateMtuOfVpcNetwork(vpcToUpdate, vpc, mtu); diff --git a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java index 729da7caa331..27facb304eba 100644 --- a/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java +++ b/server/src/main/java/com/cloud/resourcelimit/ResourceLimitManagerImpl.java @@ -1042,7 +1042,8 @@ public ResourceLimitVO updateResourceLimit(Long accountId, Long domainId, Intege ResourceLimitVO limit = _resourceLimitDao.findByOwnerIdAndTypeAndTag(ownerId, ownerType, resourceType, tag); - ActionEventUtils.onActionEvent(caller.getId(), caller.getAccountId(), + Long callingUserId = CallContext.current().getCallingUserId(); + ActionEventUtils.onActionEvent(callingUserId, caller.getAccountId(), caller.getDomainId(), EventTypes.EVENT_RESOURCE_LIMIT_UPDATE, "Resource limit updated. Resource Type: " + resourceType + ", New Value: " + max, ownerResourceId, ownerResourceType.toString()); diff --git a/server/src/main/java/com/cloud/server/ManagementServerImpl.java b/server/src/main/java/com/cloud/server/ManagementServerImpl.java index d4266496e982..e5066b6da5cc 100644 --- a/server/src/main/java/com/cloud/server/ManagementServerImpl.java +++ b/server/src/main/java/com/cloud/server/ManagementServerImpl.java @@ -1696,11 +1696,7 @@ protected List getCapableSuitableHosts( List suitableHosts = new ArrayList<>(); for (final HostAllocator allocator : hostAllocators) { - if (CollectionUtils.isNotEmpty(compatibleHosts)) { - suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, compatibleHosts, HostAllocator.RETURN_UPTO_ALL, false); - } else { - suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, HostAllocator.RETURN_UPTO_ALL, false); - } + suitableHosts = allocator.allocateTo(vmProfile, plan, Host.Type.Routing, excludes, compatibleHosts, HostAllocator.RETURN_UPTO_ALL, false); if (CollectionUtils.isNotEmpty(suitableHosts)) { break; @@ -1709,18 +1705,17 @@ protected List getCapableSuitableHosts( _dpMgr.reorderHostsByPriority(plan.getHostPriorities(), suitableHosts); - if (suitableHosts.isEmpty()) { + if (CollectionUtils.isEmpty(suitableHosts)) { logger.warn("No suitable hosts found."); - } else { - logger.debug("Hosts having capacity and suitable for migration: {}", suitableHosts); + return suitableHosts; } + logger.debug("Hosts having capacity and are suitable for migration: {}", suitableHosts); + // Only list hosts of the same architecture as the source Host in a multi-arch zone - if (!suitableHosts.isEmpty()) { - List clusterArchs = ApiDBUtils.listZoneClustersArchs(vm.getDataCenterId()); - if (CollectionUtils.isNotEmpty(clusterArchs) && clusterArchs.size() > 1) { - suitableHosts = suitableHosts.stream().filter(h -> h.getArch() == srcHost.getArch()).collect(Collectors.toList()); - } + List clusterArchs = ApiDBUtils.listZoneClustersArchs(vm.getDataCenterId()); + if (CollectionUtils.isNotEmpty(clusterArchs) && clusterArchs.size() > 1) { + suitableHosts = suitableHosts.stream().filter(h -> h.getArch() == srcHost.getArch()).collect(Collectors.toList()); } return suitableHosts; @@ -3040,7 +3035,7 @@ public Pair, Integer> listGuestOSMappingByCrit final String hypervisor = cmd.getHypervisor(); final String hypervisorVersion = cmd.getHypervisorVersion(); - //throw exception if hypervisor name is not passed, but version is + //throw exception if hypervisor name is not passed, but a version is if (hypervisorVersion != null && (hypervisor == null || hypervisor.isEmpty())) { throw new InvalidParameterValueException("Hypervisor version parameter cannot be used without specifying a hypervisor : XenServer, KVM or VMware"); } @@ -3058,7 +3053,7 @@ public Pair, Integer> listGuestOSMappingByCrit final SearchCriteria sc = sb.create(); if (id != null) { - sc.setParameters("id", SearchCriteria.Op.EQ, id); + sc.setParameters("id", id); } if (osTypeId != null) { diff --git a/server/src/main/java/com/cloud/user/AccountManagerImpl.java b/server/src/main/java/com/cloud/user/AccountManagerImpl.java index 74bf56cb0832..52ec26661c9d 100644 --- a/server/src/main/java/com/cloud/user/AccountManagerImpl.java +++ b/server/src/main/java/com/cloud/user/AccountManagerImpl.java @@ -1389,20 +1389,19 @@ public UserAccount createUserAccount(final String userName, final String passwor final String accountNameFinal = accountName; final Long domainIdFinal = domainId; - final String accountUUIDFinal = accountUUID; + final String resolvedAccountUUID = accountUUID != null ? accountUUID : UUID.randomUUID().toString(); + + // Check role escalation before the transaction — this is a read-only check + // that iterates all API commands and doesn't need a write transaction open. + AccountVO requestedAccount = new AccountVO(accountNameFinal, domainIdFinal, networkDomain, accountType, roleId, resolvedAccountUUID); + checkRoleEscalation(getCurrentCallingAccount(), requestedAccount); + Pair pair = Transaction.execute(new TransactionCallback<>() { @Override public Pair doInTransaction(TransactionStatus status) { - // create account - String accountUUID = accountUUIDFinal; - if (accountUUID == null) { - accountUUID = UUID.randomUUID().toString(); - } - AccountVO account = createAccount(accountNameFinal, accountType, roleId, domainIdFinal, networkDomain, details, accountUUID); + AccountVO account = createAccount(accountNameFinal, accountType, roleId, domainIdFinal, networkDomain, details, resolvedAccountUUID); long accountId = account.getId(); - checkRoleEscalation(getCurrentCallingAccount(), account); - // create the first user for the account UserVO user = createUser(accountId, userName, password, firstName, lastName, email, timezone, userUUID, source); @@ -3848,6 +3847,11 @@ public void buildACLViewSearchCriteria(SearchCriteria AllowDifferentHostTagsOfferingsForVmScale = new ConfigKey<>("Advanced", Boolean.class, "allow.different.host.tags.offerings.for.vm.scale", "false", "Enables/Disable allowing to change a VM offering to offerings with different host tags", true); + ConfigKey AutoMigrateVmOnLiveScaleInsufficientCapacity = new ConfigKey<>("Advanced", Boolean.class, "auto.migrate.vm.on.live.scale.insufficient.capacity", + "true", "Defines whether a VM should be automatically migrated to a suitable host when the current host " + + "lacks sufficient compute capacity to live scale the instance. Defaults to true.", true, ConfigKey.Scope.Cluster); + static final int MAX_USER_DATA_LENGTH_BYTES = 2048; public static final String CKS_NODE = "cksnode"; diff --git a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java index 70c2b97c45d5..bb55f570927b 100644 --- a/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/UserVmManagerImpl.java @@ -156,7 +156,6 @@ import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.jetbrains.annotations.NotNull; @@ -377,6 +376,7 @@ import com.cloud.utils.Journal; import com.cloud.utils.NumbersUtil; import com.cloud.utils.Pair; +import com.cloud.utils.StringUtils; import com.cloud.utils.component.ComponentContext; import com.cloud.utils.component.ManagerBase; import com.cloud.utils.concurrency.NamedThreadFactory; @@ -890,10 +890,10 @@ public UserVm resetVMPassword(ResetVMPasswordCmd cmd, String password) throws Re } private boolean resetVMPasswordInternal(Long vmId, String password) throws ResourceUnavailableException, InsufficientCapacityException { - Long userId = CallContext.current().getCallingUserId(); + long userId = CallContext.current().getCallingUserId(); VMInstanceVO vmInstance = _vmDao.findById(vmId); - if (password == null || password.equals("")) { + if (StringUtils.isEmpty(password)) { return false; } @@ -1078,7 +1078,7 @@ protected void removeEncryptedPasswordFromUserVmVoDetails(long vmId) { } private boolean resetVMSSHKeyInternal(Long vmId, String sshPublicKeys, String keypairnames) throws ResourceUnavailableException, InsufficientCapacityException { - Long userId = CallContext.current().getCallingUserId(); + long userId = CallContext.current().getCallingUserId(); VMInstanceVO vmInstance = _vmDao.findById(vmId); VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vmInstance.getTemplateId()); @@ -1250,7 +1250,7 @@ public UserVm upgradeVirtualMachine(UpgradeVMCmd cmd) throws ResourceAllocationE if (vmInstance == null) { throw new InvalidParameterValueException("unable to find an Instance with id " + vmId); } else if (!(vmInstance.getState().equals(State.Stopped))) { - throw new InvalidParameterValueException("Unable to upgrade Instance " + vmInstance.toString() + " " + " in state " + vmInstance.getState() + throw new InvalidParameterValueException("Unable to upgrade Instance " + vmInstance + " " + " in state " + vmInstance.getState() + "; make sure the Instance is stopped"); } @@ -1743,7 +1743,7 @@ public UserVm updateDefaultNicForVirtualMachine(UpdateDefaultNicForVMCmd cmd) th oldNetworkOfferingId = oldDefaultNetwork.getNetworkOfferingId(); } NicVO existingVO = _nicDao.findById(existing.id); - Integer chosenID = nic.getDeviceId(); + int chosenID = nic.getDeviceId(); Integer existingID = existing.getDeviceId(); Network newdefault = null; @@ -2116,25 +2116,26 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI int newCpu = newServiceOffering.getCpu(); int newMemory = newServiceOffering.getRamSize(); int newSpeed = newServiceOffering.getSpeed(); + boolean cpuCapEnabledForTheNewOffering = newServiceOffering.getLimitCpuUse(); int currentCpu = currentServiceOffering.getCpu(); int currentMemory = currentServiceOffering.getRamSize(); int currentSpeed = currentServiceOffering.getSpeed(); + boolean cpuCapEnabledForTheCurrentOffering = currentServiceOffering.getLimitCpuUse(); int memoryDiff = newMemory - currentMemory; int cpuDiff = newCpu * newSpeed - currentCpu * currentSpeed; - // Don't allow to scale when (Any of the new values less than current values) OR (All current and new values are same) - if ((newSpeed < currentSpeed || newMemory < currentMemory || newCpu < currentCpu) || (newSpeed == currentSpeed && newMemory == currentMemory && newCpu == currentCpu)) { - String message = String.format("While the VM is running, only scalling up it is supported. New service offering {\"memory\": %s, \"speed\": %s, \"cpu\": %s} should" - + " have at least one value (ram, speed or cpu) greater than the current values {\"memory\": %s, \"speed\": %s, \"cpu\": %s}.", newMemory, newSpeed, newCpu, - currentMemory, currentSpeed, currentCpu); - - throw new InvalidParameterValueException(message); + boolean scalingDown = newSpeed < currentSpeed || newMemory < currentMemory || newCpu < currentCpu; + if (scalingDown) { + throw new InvalidParameterValueException(String.format("Scaling down is not supported while the VM is running. The new service offering attributes " + + "{\"memory\": %s, \"CPU speed\": %s, \"vCPUs\": %s} must not be lower than the current values {\"memory\": %s, \"CPU speed\": %s, \"vCPUs\": %s}.", + newMemory, newSpeed, newCpu, currentMemory, currentSpeed, currentCpu)); } - if (vmHypervisorType.equals(HypervisorType.KVM) && !currentServiceOffering.isDynamic()) { - String message = String.format("Unable to live scale VM on KVM when current service offering is a \"Fixed Offering\". KVM needs the tag \"maxMemory\" to live scale and it is only configured when VM is deployed with a custom service offering and \"Dynamic Scalable\" is enabled."); - logger.info(message); - throw new InvalidParameterValueException(message); + boolean sameAmountOfResourcesAsThePreviousOffering = newSpeed == currentSpeed && newMemory == currentMemory && newCpu == currentCpu; + boolean cpuCapChange = cpuCapEnabledForTheCurrentOffering != cpuCapEnabledForTheNewOffering; + if (sameAmountOfResourcesAsThePreviousOffering && (vmHypervisorType != HypervisorType.KVM || !cpuCapChange)) { + throw new InvalidParameterValueException("While the VM is running, scaling to a service offering with the same attributes (memory, CPU speed and vCPUs) " + + "is only allowed when the CPU cap is changed."); } serviceOfferingDao.loadDetails(currentServiceOffering); @@ -2171,14 +2172,14 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI // Check vm flag if (!vmInstance.isDynamicallyScalable()) { - throw new CloudRuntimeException(String.format("Unable to scale %s as it does not have tools to support dynamic scaling.", vmInstance.toString())); + throw new CloudRuntimeException(String.format("Unable to scale %s as it does not have tools to support dynamic scaling.", vmInstance)); } // Check disable threshold for cluster is not crossed HostVO host = _hostDao.findById(vmInstance.getHostId()); _hostDao.loadDetails(host); if (_capacityMgr.checkIfClusterCrossesThreshold(host.getClusterId(), cpuDiff, memoryDiff)) { - throw new CloudRuntimeException(String.format("Unable to scale %s due to insufficient resources.", vmInstance.toString())); + throw new CloudRuntimeException(String.format("Unable to scale %s due to insufficient resources.", vmInstance)); } while (retry-- != 0) { // It's != so that it can match -1. @@ -2200,8 +2201,19 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI excludes.addHost(vmInstance.getHostId()); } + boolean autoMigrateVmToASuitableHost = AutoMigrateVmOnLiveScaleInsufficientCapacity.valueIn(host.getClusterId()); + if (!existingHostHasCapacity && !autoMigrateVmToASuitableHost) { + logger.error("Unable to scale the VM [{}] because the host [{}] in which it is currently allocated does not " + + "have enough compute capacity to scale the instance and the VM should not be automatically migrated to another host " + + "([{}] setting is [false]).", vmInstance.getInstanceName(), host.getName(), AutoMigrateVmOnLiveScaleInsufficientCapacity.key()); + return false; + } + // #2 migrate the vm if host doesn't have capacity or is in avoid set if (!existingHostHasCapacity) { + logger.info("Host [{}] does not have enough compute capacity to scale the instance [{}]. Since the [{}] setting is " + + "[true], the VM will be migrated to a suitable host and, if succeeded, the VM will be live scaled to the requested " + + "compute offering.", host.getName(), vmInstance.getInstanceName(), AutoMigrateVmOnLiveScaleInsufficientCapacity.key()); _itMgr.findHostAndMigrate(vmInstance.getUuid(), newServiceOfferingId, customParameters, excludes); } @@ -2215,7 +2227,7 @@ private boolean upgradeRunningVirtualMachine(Long vmId, Long newServiceOfferingI success = true; return success; } catch (InsufficientCapacityException | ResourceUnavailableException | ConcurrentOperationException e) { - logger.error(String.format("Unable to scale %s due to [%s].", vmInstance.toString(), e.getMessage()), e); + logger.error(String.format("Unable to scale %s due to [%s].", vmInstance, e.getMessage()), e); } finally { if (!success) { // Decrement CPU and Memory count accordingly. @@ -4349,9 +4361,7 @@ protected List getResourceLimitStorageTags(long diskOfferingId) { return resourceLimitService.getResourceLimitStorageTags(diskOfferingVO); } - private List reserveStorageResourcesForVm(Account owner, Long diskOfferingId, Long diskSize, List dataDiskInfoList, Long rootDiskOfferingId, ServiceOfferingVO offering, Long rootDiskSize) throws ResourceAllocationException { - List checkedReservations = new ArrayList<>(); - + private void reserveStorageResourcesForVm(List checkedReservations, Account owner, Long diskOfferingId, Long diskSize, List dataDiskInfoList, Long rootDiskOfferingId, ServiceOfferingVO offering, Long rootDiskSize) throws ResourceAllocationException { List rootResourceLimitStorageTags = getResourceLimitStorageTags(rootDiskOfferingId != null ? rootDiskOfferingId : offering.getDiskOfferingId()); CheckedReservation rootVolumeReservation = new CheckedReservation(owner, ResourceType.volume, rootResourceLimitStorageTags, 1L, reservationDao, resourceLimitService); checkedReservations.add(rootVolumeReservation); @@ -4359,12 +4369,12 @@ private List reserveStorageResourcesForVm(Account owner, Lon checkedReservations.add(rootPrimaryStorageReservation); if (diskOfferingId != null) { - List additionalResourceLimitStorageTags = diskOfferingId != null ? getResourceLimitStorageTags(diskOfferingId) : null; + List additionalResourceLimitStorageTags = getResourceLimitStorageTags(diskOfferingId); DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); Long size = verifyAndGetDiskSize(diskOffering, diskSize); - CheckedReservation additionalVolumeReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTags, 1L, reservationDao, resourceLimitService) : null; + CheckedReservation additionalVolumeReservation = new CheckedReservation(owner, ResourceType.volume, additionalResourceLimitStorageTags, 1L, reservationDao, resourceLimitService); checkedReservations.add(additionalVolumeReservation); - CheckedReservation additionalPrimaryStorageReservation = diskOfferingId != null ? new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTags, size, reservationDao, resourceLimitService) : null; + CheckedReservation additionalPrimaryStorageReservation = new CheckedReservation(owner, ResourceType.primary_storage, additionalResourceLimitStorageTags, size, reservationDao, resourceLimitService); checkedReservations.add(additionalPrimaryStorageReservation); } @@ -4380,7 +4390,6 @@ private List reserveStorageResourcesForVm(Account owner, Lon checkedReservations.add(additionalPrimaryStorageReservation); } } - return checkedReservations; } private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, String displayName, Account owner, @@ -4392,10 +4401,10 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri Map userVmOVFPropertiesMap, boolean dynamicScalingEnabled, String vmType, VMTemplateVO template, HypervisorType hypervisorType, long accountId, ServiceOfferingVO offering, boolean isIso, Long rootDiskOfferingId, long volumesSize, Volume volume, Snapshot snapshot) throws ResourceAllocationException { - List checkedReservations = new ArrayList<>(); + List checkedReservations = new ArrayList<>(); try { - checkedReservations = reserveStorageResourcesForVm(owner, diskOfferingId, diskSize, dataDiskInfoList, rootDiskOfferingId, offering, volumesSize); + reserveStorageResourcesForVm(checkedReservations, owner, diskOfferingId, diskSize, dataDiskInfoList, rootDiskOfferingId, offering, volumesSize); // verify security group ids if (securityGroupIdList != null) { @@ -4536,7 +4545,7 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri } NetworkOffering ntwkOffering = _networkOfferingDao.findById(network.getNetworkOfferingId()); - Long physicalNetworkId = _networkModel.findPhysicalNetworkId(zone.getId(), ntwkOffering.getTags(), ntwkOffering.getTrafficType()); + long physicalNetworkId = _networkModel.findPhysicalNetworkId(zone.getId(), ntwkOffering.getTags(), ntwkOffering.getTrafficType()); String provider = _ntwkSrvcDao.getProviderForServiceInNetwork(network.getId(), Service.Connectivity); if (!_networkModel.isProviderEnabledInPhysicalNetwork(physicalNetworkId, provider)) { @@ -4686,14 +4695,7 @@ private UserVm getUncheckedUserVmResource(DataCenter zone, String hostName, Stri logger.error("error during resource reservation and allocation", e); throw new CloudRuntimeException(e); } finally { - for (CheckedReservation checkedReservation : checkedReservations) { - try { - checkedReservation.close(); - } catch (Exception e) { - logger.error("error during resource reservation and allocation", e); - throw new CloudRuntimeException(e); - } - } + ReservationHelper.closeAll(checkedReservations); } } @@ -4761,7 +4763,7 @@ protected long configureCustomRootDiskSize(Map customParameters, } if (customParameters.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { - Long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1); + long rootDiskSize = NumbersUtil.parseLong(customParameters.get(VmDetailConstants.ROOT_DISK_SIZE), -1); if (rootDiskSize <= 0) { throw new InvalidParameterValueException("Root disk size should be a positive number."); } @@ -4880,7 +4882,7 @@ private UserVmVO commitUserVm(final boolean isImport, final DataCenter zone, fin final Map> extraDhcpOptionMap, final Map dataDiskTemplateToDiskOfferingMap, final Map userVmOVFPropertiesMap, final VirtualMachine.PowerState powerState, final boolean dynamicScalingEnabled, String vmType, final Long rootDiskOfferingId, String sshkeypairs, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { - Long selectedGuestOsId = guestOsId != null ? guestOsId : template.getGuestOSId(); + long selectedGuestOsId = guestOsId != null ? guestOsId : template.getGuestOSId(); UserVmVO vm = new UserVmVO(id, instanceName, displayName, template.getId(), hypervisorType, selectedGuestOsId, offering.isOfferHA(), offering.getLimitCpuUse(), owner.getDomainId(), owner.getId(), userId, offering.getId(), userData, userDataId, userDataDetails, hostName); vm.setUuid(uuidName); @@ -5439,7 +5441,7 @@ private void addUserVMCmdlineArgs(Long vmId, VirtualMachineProfile profile, Depl if (dc.getDns2() != null) { buf.append(" dns2=").append(dc.getDns2()); } - logger.info("cmdline details: "+ buf.toString()); + logger.info("cmdline details: "+ buf); } @Override @@ -5699,7 +5701,7 @@ private void checkForceStopVmPermission(Account callingAccount) { public UserVm stopVirtualMachine(long vmId, boolean forced) throws ConcurrentOperationException { // Input validation Account caller = CallContext.current().getCallingAccount(); - Long userId = CallContext.current().getCallingUserId(); + long userId = CallContext.current().getCallingUserId(); // if account is removed, return error if (caller != null && caller.getRemoved() != null) { @@ -6471,7 +6473,13 @@ public UserVm createVirtualMachine(DeployVMCmd cmd) throws InsufficientCapacityE } _accountMgr.checkAccess(caller, null, true, snapshot); VolumeInfo volumeOfSnapshot = getVolume(snapshot.getVolumeId(), templateId, true); - templateId = volumeOfSnapshot.getTemplateId(); + if (volumeOfSnapshot != null) { + templateId = volumeOfSnapshot.getTemplateId(); + } else if (templateId == null) { + throw new InvalidParameterValueException( + "Could not determine template from snapshot id=" + cmd.getSnapshotId() + + "; the source volume no longer exists. Please specify a templateId."); + } } VirtualMachineTemplate template = null; @@ -6753,7 +6761,9 @@ protected void addLeaseDetailsForInstance(UserVm vm, Integer leaseDuration, VMLe private VolumeInfo getVolume(long id, Long templateId, boolean isSnapshot) { VolumeInfo volume = volFactory.getVolume(id); if (volume != null) { - if (volume.getDataStore() == null || !ScopeType.ZONE.equals(volume.getDataStore().getScope().getScopeType())) { + if (!isSnapshot + && (volume.getDataStore() == null + || !ScopeType.ZONE.equals(volume.getDataStore().getScope().getScopeType()))) { throw new InvalidParameterValueException("Deployment of virtual machine is supported only for Zone-wide storage pools"); } checkIfVolumeTemplateIsTheSameAsTheProvided(volume, templateId); @@ -6969,7 +6979,7 @@ protected void addExtraConfig(UserVm vm, String extraConfig) { } else if (hypervisorType.equals(HypervisorType.VMware)) { persistExtraConfigVmware(decodedUrl, vm); } else { - String msg = String.format("This hypervisor %s is not supported for use with this feature", hypervisorType.toString()); + String msg = String.format("This hypervisor %s is not supported for use with this feature", hypervisorType); throw new CloudRuntimeException(msg); } } @@ -7315,7 +7325,7 @@ private DeployDestination chooseVmMigrationDestination(VMInstanceVO vm, Host src vm.setLastHostId(null); // Last host does not have higher priority in vm migration final ServiceOfferingVO offering = serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm, null, offering, null, null); - final Long srcHostId = srcHost.getId(); + final long srcHostId = srcHost.getId(); final Host host = _hostDao.findById(srcHostId); ExcludeList excludes = new ExcludeList(); excludes.addHost(srcHostId); @@ -7968,7 +7978,7 @@ public UserVm moveVmToUser(final AssignVMCmd cmd) throws ResourceAllocationExcep Long domainId = cmd.getDomainId(); Long projectId = cmd.getProjectId(); - Long oldAccountId = vm.getAccountId(); + long oldAccountId = vm.getAccountId(); String newAccountName = cmd.getAccountName(); final Account oldAccount = _accountService.getActiveAccountById(oldAccountId); final Account newAccount = _accountMgr.finalizeOwner(caller, newAccountName, domainId, projectId); @@ -8597,7 +8607,7 @@ protected void selectApplicableNetworkToCreateVm(Account newAccount, DataCenterV protected void addDefaultSecurityGroupToSecurityGroupIdList(Account newAccount, List securityGroupIdList) { logger.debug("Adding default security group to security group list if not already in it."); - Long newAccountId = newAccount.getId(); + long newAccountId = newAccount.getId(); SecurityGroup defaultGroup = _securityGroupMgr.getDefaultSecurityGroup(newAccountId); boolean defaultGroupPresent = false; @@ -8744,7 +8754,7 @@ protected NetworkVO createApplicableNetworkToCreateVm(Account newAccount, DataCe logger.trace("Creating an applicable network to create the VM."); NetworkVO defaultNetwork; - Long zoneId = zone.getId(); + long zoneId = zone.getId(); Account caller = CallContext.current().getCallingAccount(); NetworkOfferingVO requiredOffering = getOfferingWithRequiredAvailabilityForNetworkCreation(); String requiredOfferingTags = requiredOffering.getTags(); @@ -9468,7 +9478,7 @@ public ConfigKey[] getConfigKeys() { VmIpFetchThreadPoolMax, VmIpFetchTaskWorkers, AllowDeployVmIfGivenHostFails, EnableAdditionalVmConfig, DisplayVMOVFProperties, KvmAdditionalConfigAllowList, XenServerAdditionalConfigAllowList, VmwareAdditionalConfigAllowList, DestroyRootVolumeOnVmDestruction, EnforceStrictResourceLimitHostTagCheck, StrictHostTags, AllowUserForceStopVm, VmDistinctHostNameScope, - VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale}; + VmwareAdditionalDetailsFromOvaEnabled, VmwareAllowedAdditionalDetailsFromOva, AllowDifferentHostTagsOfferingsForVmScale, AutoMigrateVmOnLiveScaleInsufficientCapacity}; } @Override @@ -9504,7 +9514,7 @@ private boolean checkStatusOfVolumeSnapshots(VirtualMachine vm, Volume.Type type } logger.debug("Found {} no. of volumes of type {} for vm with VM ID {}", listVolumes.size(), type, vm); for (VolumeVO volume : listVolumes) { - Long volumeId = volume.getId(); + long volumeId = volume.getId(); logger.debug("Checking status of snapshots for Volume: {}", volume); List ongoingSnapshots = _snapshotDao.listByStatus(volumeId, Snapshot.State.Creating, Snapshot.State.CreatedOnPrimary, Snapshot.State.BackingUp); int ongoingSnapshotsCount = ongoingSnapshots.size(); @@ -9523,12 +9533,12 @@ private void checkForUnattachedVolumes(long vmId, List volumes) { for (VolumeVO volume : volumes) { if (volume.getInstanceId() == null || vmId != volume.getInstanceId() || volume.getVolumeType() != Volume.Type.DATADISK) { - sb.append(volume.toString() + "; "); + sb.append(volume + "; "); } } if (!StringUtils.isEmpty(sb.toString())) { - throw new InvalidParameterValueException("The following supplied volumes are not DATADISK attached to the VM: " + sb.toString()); + throw new InvalidParameterValueException("The following supplied volumes are not DATADISK attached to the VM: " + sb); } } @@ -9536,7 +9546,7 @@ private void validateVolumes(List volumes) { for (VolumeVO volume : volumes) { if (!(volume.getVolumeType() == Volume.Type.ROOT || volume.getVolumeType() == Volume.Type.DATADISK)) { - throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK.toString() + " or " + Volume.Type.ROOT.toString()); + throw new InvalidParameterValueException("Please specify volume of type " + Volume.Type.DATADISK + " or " + Volume.Type.ROOT); } if (volume.isDeleteProtection()) { throw new InvalidParameterValueException(String.format( @@ -9630,7 +9640,7 @@ public UserVm importVM(final DataCenter zone, final Host host, final VirtualMach final String uuidName = _uuidMgr.generateUuid(UserVm.class, null); final Host lastHost = powerState != VirtualMachine.PowerState.PowerOn ? host : null; - final Boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId()); + final boolean dynamicScalingEnabled = checkIfDynamicScalingCanBeEnabled(null, serviceOffering, template, zone.getId()); return commitUserVm(true, zone, host, lastHost, template, hostName, displayName, owner, null, null, userData, null, null, isDisplayVm, keyboard, accountId, userId, serviceOffering, template.getFormat().equals(ImageFormat.ISO), guestOsId, sshPublicKeys, networkNicMap, @@ -9692,7 +9702,7 @@ private void updateDetailsWithRootDiskAttributes(Map details, Vm } private void checkRootDiskSizeAgainstBackup(Long instanceVolumeSize,DiskOffering rootDiskOffering, Long backupVolumeSize) { - Long instanceRootDiskSize = rootDiskOffering.isCustomized() ? instanceVolumeSize : rootDiskOffering.getDiskSize() / GiB_TO_BYTES; + long instanceRootDiskSize = rootDiskOffering.isCustomized() ? instanceVolumeSize : rootDiskOffering.getDiskSize() / GiB_TO_BYTES; if (instanceRootDiskSize < backupVolumeSize) { throw new InvalidParameterValueException( String.format("Instance volume root disk size %d[GiB] cannot be less than the backed-up volume size %d[GiB].", @@ -9769,7 +9779,7 @@ public UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws Insufficien Long size = cmd.getSize(); Long diskOfferingId = cmd.getDiskOfferingId(); - Boolean isIso = template.getFormat().equals(ImageFormat.ISO); + boolean isIso = template.getFormat().equals(ImageFormat.ISO); if (diskOfferingId != null) { if (!isIso) { throw new InvalidParameterValueException(ApiConstants.DISK_OFFERING_ID + " parameter is supported for creating instance from backup only for ISO. For creating VMs with templates, please use the parameter " + ApiConstants.DATADISKS_DETAILS); diff --git a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java index a6f14c8ea962..52e2434d3206 100644 --- a/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java +++ b/server/src/main/java/com/cloud/vm/snapshot/VMSnapshotManagerImpl.java @@ -765,6 +765,13 @@ public UserVm revertToSnapshot(Long vmSnapshotId) throws InsufficientCapacityExc "In order to revert to a Snapshot without memory you need to first stop the Instance."); } + if (userVm.getState() == VirtualMachine.State.Running && vmSnapshotVo.getType() == VMSnapshot.Type.Disk) { + throw new InvalidParameterValueException( + "Reverting to the Instance Snapshot is not allowed for running Instances as this would result in an Instance state change. " + + "For running Instances only Snapshots with memory can be reverted. " + + "In order to revert to a Snapshot without memory you need to first stop the Instance."); + } + if (userVm.getState() == VirtualMachine.State.Stopped && vmSnapshotVo.getType() == VMSnapshot.Type.DiskAndMemory) { throw new InvalidParameterValueException( "Reverting to the Instance Snapshot is not allowed for stopped Instances when the Snapshot contains memory as this would result in an Instance state change. " + diff --git a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java index fd4fc9466066..1cbb668206d9 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/BackupManagerImpl.java @@ -576,12 +576,12 @@ public String createVolumeInfoFromVolumes(List vmVolumes) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign VM to backup offering", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, eventDescription = "assign Instance to Backup Offering", async = true) public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { final VMInstanceVO vm = findVmById(vmId); if (!Arrays.asList(VirtualMachine.State.Running, VirtualMachine.State.Stopped, VirtualMachine.State.Shutdown).contains(vm.getState())) { - throw new CloudRuntimeException("VM is not in running or stopped state"); + throw new CloudRuntimeException("Instance is not in running or stopped state"); } validateBackupForZone(vm.getDataCenterId()); @@ -589,7 +589,7 @@ public boolean assignVMToBackupOffering(Long vmId, Long offeringId) { accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() != null) { - throw new CloudRuntimeException("VM already is assigned to a backup offering, please remove the VM from its previous offering"); + throw new CloudRuntimeException("Instance already is assigned to a backup offering, please remove the Instance from its previous offering"); } final BackupOfferingVO offering = backupOfferingDao.findById(offeringId); @@ -621,12 +621,12 @@ public VMInstanceVO doInTransaction(final TransactionStatus status) { vm.setBackupVolumes(createVolumeInfoFromVolumes(new ArrayList<>(volumeDao.findByInstance(vmId)))); if (!backupProvider.assignVMToBackupOffering(vm, offering)) { - throw new CloudRuntimeException("Failed to assign the VM to the backup offering, please try removing the assignment and try again."); + throw new CloudRuntimeException("Failed to assign the Instance to the Backup Offering, please try removing the assignment and try again."); } if (!vmInstanceDao.update(vmId, vm)) { backupProvider.removeVMFromBackupOffering(vm); - throw new CloudRuntimeException("Failed to update VM assignment to the backup offering in the DB, please try again."); + throw new CloudRuntimeException("Failed to update Instance assignment to the Backup Offering in the DB, please try again."); } UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VM_BACKUP_OFFERING_ASSIGN, vm.getAccountId(), vm.getDataCenterId(), vmId, @@ -635,7 +635,7 @@ public VMInstanceVO doInTransaction(final TransactionStatus status) { "uuid", "instanceName", "backupOfferingId", "backupVolumes"), ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offering, "uuid", "name", "externalId", "provider"))); } catch (Exception e) { - String msg = String.format("Failed to assign VM [%s] to the Backup Offering [%s], using provider [name: %s, class: %s], due to: [%s].", + String msg = String.format("Failed to assign Instance [%s] to the Backup Offering [%s], using provider [name: %s, class: %s], due to: [%s].", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(vm, "uuid", "instanceName", "backupOfferingId", "backupVolumes"), ReflectionToStringBuilderUtils.reflectOnlySelectedFields(offering, "uuid", "name", "externalId", "provider"), backupProvider.getName(), backupProvider.getClass().getSimpleName(), e.getMessage()); @@ -649,11 +649,11 @@ public VMInstanceVO doInTransaction(final TransactionStatus status) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove VM from backup offering", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_OFFERING_REMOVE, eventDescription = "remove Instance from Backup Offering", async = true) public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) { final VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(vmId); if (vm == null) { - throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId)); + throw new CloudRuntimeException(String.format("Can't find any Instance with ID: [%s].", vmId)); } validateBackupForZone(vm.getDataCenterId()); @@ -670,8 +670,8 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) } if (!forced && backupProvider.willDeleteBackupsOnOfferingRemoval()) { - String message = String.format("To remove VM [id: %s, name: %s] from Backup Offering [id: %s, name: %s] using the provider [%s], please specify the " - + "forced:true option to allow the deletion of all jobs and backups for this VM or remove the backups that this VM has with the backup " + String message = String.format("To remove Instance [id: %s, name: %s] from Backup Offering [id: %s, name: %s] using the provider [%s], please specify the " + + "forced:true option to allow the deletion of all jobs and backups for this Instance or remove the backups that this Instance has with the backup " + "offering.", vm.getUuid(), vm.getInstanceName(), offering.getUuid(), offering.getName(), backupProvider.getClass().getSimpleName()); throw new CloudRuntimeException(message); } @@ -709,7 +709,7 @@ public boolean removeVMFromBackupOffering(final Long vmId, final boolean forced) } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring VM backup schedule") + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_CONFIGURE, eventDescription = "configuring Instance Backup Schedule") public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { final Long vmId = cmd.getVmId(); final DateUtil.IntervalType intervalType = cmd.getIntervalType(); @@ -725,7 +725,7 @@ public BackupSchedule configureBackupSchedule(CreateBackupScheduleCmd cmd) { accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() == null) { - throw new CloudRuntimeException("Cannot configure backup schedule for the VM without having any backup offering"); + throw new CloudRuntimeException("Cannot configure Backup Schedule for the Instance as it is not assigned any Backup Offering"); } final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId()); @@ -859,7 +859,7 @@ public List listBackupSchedules(ListBackupScheduleCmd cmd) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting VM backup schedule") + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_SCHEDULE_DELETE, eventDescription = "deleting Instance Backup Schedule") public boolean deleteBackupSchedule(DeleteBackupScheduleCmd cmd) { Long vmId = cmd.getVmId(); Long id = cmd.getId(); @@ -910,7 +910,7 @@ protected boolean deleteAllVmBackupSchedules(long vmId) { } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating VM backup", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_CREATE, eventDescription = "creating Instance Backup", async = true) public boolean createBackup(CreateBackupCmd cmd, Object job) throws ResourceAllocationException { Long vmId = cmd.getVmId(); Account caller = CallContext.current().getCallingAccount(); @@ -920,17 +920,17 @@ public boolean createBackup(CreateBackupCmd cmd, Object job) throws ResourceAllo accountManager.checkAccess(caller, null, true, vm); if (vm.getBackupOfferingId() == null) { - throw new CloudRuntimeException("VM has not backup offering configured, cannot create backup before assigning it to a backup offering"); + throw new CloudRuntimeException("Cannot create backup as the Instance doesn't have a Backup Offering assigned"); } final BackupOffering offering = backupOfferingDao.findById(vm.getBackupOfferingId()); if (offering == null) { - throw new CloudRuntimeException("VM backup offering not found"); + throw new CloudRuntimeException("Instance Backup Offering not found"); } final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (backupProvider == null) { - throw new CloudRuntimeException("VM backup provider not found for the offering"); + throw new CloudRuntimeException("Instance backup provider not found for the Offering"); } if (!offering.isUserDrivenBackupAllowed()) { @@ -970,14 +970,9 @@ private void createCheckedBackup(CreateBackupCmd cmd, Account owner, boolean isS CheckedReservation backupStorageReservation = new CheckedReservation(owner, Resource.ResourceType.backup_storage, backupSize, reservationDao, resourceLimitMgr)) { - ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), - EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), - vmId, ApiCommandResourceType.VirtualMachine.toString(), - true, 0); - Pair result = backupProvider.takeBackup(vm, cmd.getQuiesceVM()); if (!result.first()) { - throw new CloudRuntimeException("Failed to create VM backup"); + throw new CloudRuntimeException("Failed to create Instance Backup"); } Backup backup = result.second(); if (backup != null) { @@ -1079,7 +1074,7 @@ protected void deleteExcessBackups(List backups, int amountOfBackupsTo for (int i = 0; i < amountOfBackupsToDelete; i++) { BackupVO backup = backups.get(i); if (deleteBackup(backup.getId(), false)) { - String eventDescription = String.format("Successfully deleted backup for VM [ID: %s], suiting the retention specified in the backup schedule [ID: %s]", backup.getVmId(), backupScheduleId); + String eventDescription = String.format("Successfully deleted Backup for Instance [ID: %s], suiting the retention specified in the Backup Schedule [ID: %s]", backup.getVmId(), backupScheduleId); logger.info(eventDescription); ActionEventUtils.onCompletedActionEvent( User.UID_SYSTEM, backup.getAccountId(), EventVO.LEVEL_INFO, @@ -1176,9 +1171,9 @@ public boolean importRestoredVM(long zoneId, long domainId, long accountId, long logger.error(String.format("Failed to import VM [vmInternalName: %s] from backup restoration [%s] with hypervisor [type: %s] due to: [%s].", vmInternalName, ReflectionToStringBuilderUtils.reflectOnlySelectedFields(backup, "id", "uuid", "vmId", "externalId", "backupType"), hypervisorType, e.getMessage()), e); ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE, - String.format("Failed to import VM %s from backup %s with hypervisor [type: %s]", vmInternalName, backup.getUuid(), hypervisorType), + String.format("Failed to import Instance %s from Backup %s with hypervisor [type: %s]", vmInternalName, backup.getUuid(), hypervisorType), vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); - throw new CloudRuntimeException("Error during vm backup restoration and import: " + e.getMessage()); + throw new CloudRuntimeException("Error during Instance Backup restoration and import: " + e.getMessage()); } if (vm == null) { String message = String.format("Failed to import restored VM %s with hypervisor type %s using backup of VM ID %s", @@ -1188,14 +1183,14 @@ public boolean importRestoredVM(long zoneId, long domainId, long accountId, long message, vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); } else { ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_RESTORE, - String.format("Restored VM %s from backup %s", vm.getUuid(), backup.getUuid()), + String.format("Restored Instance %s from Backup %s", vm.getUuid(), backup.getUuid()), vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); } return vm != null; } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring Instance from Backup", async = true) public boolean restoreBackup(final Long backupId) { final BackupVO backup = backupDao.findById(backupId); if (backup == null) { @@ -1214,7 +1209,7 @@ public boolean restoreBackup(final Long backupId) { if (vm.getRemoved() == null && !vm.getState().equals(VirtualMachine.State.Stopped) && !vm.getState().equals(VirtualMachine.State.Destroyed)) { - throw new CloudRuntimeException("Existing VM should be stopped before being restored from backup"); + throw new CloudRuntimeException("Existing Instance should be stopped before being restored from Backup"); } // This is done to handle historic backups if any with Veeam / Networker plugins @@ -1222,7 +1217,7 @@ public boolean restoreBackup(final Long backupId) { vm.getBackupVolumeList() : backup.getBackedUpVolumes(); List vmVolumes = volumeDao.findByInstance(vm.getId()); if (vmVolumes.size() != backupVolumes.size()) { - throw new CloudRuntimeException("Unable to restore VM with the current backup as the backup has different number of disks as the VM"); + throw new CloudRuntimeException("Unable to restore Instance with the current Backup as the Backup has different number of disks to the Instance"); } BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(vm.getBackupOfferingId()); @@ -1256,16 +1251,16 @@ protected void tryRestoreVM(BackupVO backup, VMInstanceVO vm, BackupOffering off updateVmState(vm, VirtualMachine.Event.RestoringRequested, VirtualMachine.State.Restoring); updateVolumeState(vm, Volume.Event.RestoreRequested, Volume.State.Restoring); ActionEventUtils.onStartedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventTypes.EVENT_VM_BACKUP_RESTORE, - String.format("Restoring VM %s from backup %s", vm.getUuid(), backup.getUuid()), + String.format("Restoring Instance %s from Backup %s", vm.getUuid(), backup.getUuid()), vm.getId(), ApiCommandResourceType.VirtualMachine.toString(), true, 0); final BackupProvider backupProvider = getBackupProvider(offering.getProvider()); if (!backupProvider.restoreVMFromBackup(vm, backup)) { ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_ERROR, EventTypes.EVENT_VM_BACKUP_RESTORE, - String.format("Failed to restore VM %s from backup %s", vm.getInstanceName(), backup.getUuid()), + String.format("Failed to restore Instance %s from Backup %s", vm.getInstanceName(), backup.getUuid()), vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); - throw new CloudRuntimeException("Error restoring VM from backup with uuid " + backup.getUuid()); + throw new CloudRuntimeException("Error restoring Instance from Backup with uuid " + backup.getUuid()); } // The restore process is executed by a backup provider outside of ACS, I am using the catch-all (Exception) to // ensure that no provider-side exception is missed. Therefore, we have a proper handling of exceptions, and rollbacks if needed. @@ -1273,7 +1268,7 @@ protected void tryRestoreVM(BackupVO backup, VMInstanceVO vm, BackupOffering off logger.error(String.format("Failed to restore backup [%s] due to: [%s].", backupDetailsInMessage, e.getMessage()), e); updateVolumeState(vm, Volume.Event.RestoreFailed, Volume.State.Ready); updateVmState(vm, VirtualMachine.Event.RestoringFailed, VirtualMachine.State.Stopped); - throw new CloudRuntimeException(String.format("Error restoring VM from backup [%s].", backupDetailsInMessage)); + throw new CloudRuntimeException(String.format("Error restoring Instance from Backup [%s].", backupDetailsInMessage)); } } @@ -1288,10 +1283,10 @@ private void updateVmState(VMInstanceVO vm, VirtualMachine.Event event, VirtualM Transaction.execute(TransactionLegacy.CLOUD_DB, (TransactionCallback) status -> { try { if (!virtualMachineManager.stateTransitTo(vm, event, vm.getHostId())) { - throw new CloudRuntimeException(String.format("Unable to change state of VM [%s] to [%s].", vm, next)); + throw new CloudRuntimeException(String.format("Unable to change state of Instance [%s] to [%s].", vm, next)); } } catch (NoTransitionException e) { - String errMsg = String.format("Failed to update state of VM [%s] with event [%s] due to [%s].", vm, event, e.getMessage()); + String errMsg = String.format("Failed to update state of Instance [%s] with event [%s] due to [%s].", vm, event, e.getMessage()); logger.error(errMsg, e); throw new RuntimeException(errMsg); } @@ -1503,7 +1498,7 @@ public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws Cl throw new CloudRuntimeException("Instance with ID " + backup.getVmId() + " couldn't be found."); } if (!vm.getState().equals(VirtualMachine.State.Stopped)) { - throw new CloudRuntimeException("The VM should be in stopped state"); + throw new CloudRuntimeException("The Instance should be in stopped state"); } List backupVolumes = backup.getBackedUpVolumes(); @@ -1567,7 +1562,7 @@ public boolean restoreBackupToVM(final Long backupId, final Long vmId) throws Cl } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE, eventDescription = "restoring VM from backup", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM, eventDescription = "restoring Volume from Backup to Instance", async = true) public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception { if (StringUtils.isEmpty(backedUpVolumeUuid)) { throw new CloudRuntimeException("Invalid volume ID passed"); @@ -1585,7 +1580,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); if (vm.getBackupOfferingId() != null && !BackupEnableAttachDetachVolumes.value()) { - throw new CloudRuntimeException("The selected VM has backups, cannot restore and attach volume to the VM."); + throw new CloudRuntimeException("The selected Instance has backups, cannot restore and attach Volume to the Instance."); } if (backup.getZoneId() != vm.getDataCenterId()) { @@ -1596,7 +1591,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, if (volumeInfoList == null) { final VMInstanceVO vmFromBackup = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); if (vmFromBackup == null) { - throw new CloudRuntimeException("VM reference for the provided VM backup not found"); + throw new CloudRuntimeException("Instance reference for the provided Instance backup not found"); } else if (vmFromBackup == null || vmFromBackup.getBackupVolumeList() == null) { throw new CloudRuntimeException("Volumes metadata not found in the backup"); } @@ -1610,7 +1605,7 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, accountManager.checkAccess(CallContext.current().getCallingAccount(), null, true, vm); final BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(backup.getBackupOfferingId()); if (offering == null) { - throw new CloudRuntimeException("Failed to find VM backup offering"); + throw new CloudRuntimeException("Failed to find Instance Backup Offering"); } BackupProvider backupProvider = getBackupProvider(offering.getProvider()); @@ -1636,12 +1631,12 @@ public boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, Pair result = restoreBackedUpVolume(backupVolumeInfo, backup, backupProvider, hostPossibleValues, datastoresPossibleValues, vm); if (BooleanUtils.isFalse(result.first())) { - throw new CloudRuntimeException(String.format("Error restoring volume [%s] of VM [%s] to host [%s] using backup provider [%s] due to: [%s].", + throw new CloudRuntimeException(String.format("Error restoring Volume [%s] of Instance [%s] to host [%s] using backup provider [%s] due to: [%s].", backedUpVolumeUuid, vm.getUuid(), host.getUuid(), backupProvider.getName(), result.second())); } if (!attachVolumeToVM(vm.getDataCenterId(), result.second(), backupVolumeInfo, backedUpVolumeUuid, vm, datastore.getUuid(), backup)) { - throw new CloudRuntimeException(String.format("Error attaching volume [%s] to VM [%s].", backedUpVolumeUuid, vm.getUuid())); + throw new CloudRuntimeException(String.format("Error attaching Volume [%s] to Instance [%s].", backedUpVolumeUuid, vm.getUuid())); } return true; } @@ -1670,7 +1665,7 @@ protected Pair restoreBackedUpVolume(final Backup.VolumeInfo ba } @Override - @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting VM backup", async = true) + @ActionEvent(eventType = EventTypes.EVENT_VM_BACKUP_DELETE, eventDescription = "deleting Instance backup", async = true) public boolean deleteBackup(final Long backupId, final Boolean forced) throws ResourceAllocationException { final BackupVO backup = backupDao.findByIdIncludingRemoved(backupId); if (backup == null) { @@ -1776,12 +1771,12 @@ private boolean attachVolumeToVM(Long zoneId, String restoredVolumeLocation, Bac HypervisorGuru guru = hypervisorGuruManager.getGuru(vm.getHypervisorType()); backupVolumeInfo.setType(Volume.Type.DATADISK); - logger.info("Attaching the restored volume {} to VM {}.", () -> ReflectionToStringBuilder.toString(backupVolumeInfo, ToStringStyle.JSON_STYLE), () -> vm); + logger.info("Attaching the restored Volume {} to Instance {}.", () -> ReflectionToStringBuilder.toString(backupVolumeInfo, ToStringStyle.JSON_STYLE), () -> vm); StoragePoolVO pool = primaryDataStoreDao.findByUuid(datastoreUuid); try { return guru.attachRestoredVolumeToVirtualMachine(zoneId, restoredVolumeLocation, backupVolumeInfo, vm, pool.getId(), backup); } catch (Exception e) { - throw new CloudRuntimeException("Error attach restored volume to VM " + vm.getUuid() + " due to: " + e.getMessage()); + throw new CloudRuntimeException("Error attach restored Volume to Instance " + vm.getUuid() + " due to: " + e.getMessage()); } } @@ -1964,7 +1959,7 @@ private void checkStatusOfCurrentlyExecutingBackups() { case FAILED: final Date nextDateTime = scheduleNextBackupJob(backupSchedule); final String nextScheduledTime = DateUtil.displayDateInTimezone(DateUtil.GMT_TIMEZONE, nextDateTime); - logger.debug("Next backup scheduled time for VM ID " + backupSchedule.getVmId() + " is " + nextScheduledTime); + logger.debug("Next backup scheduled time for Instance ID " + backupSchedule.getVmId() + " is " + nextScheduledTime); break; default: logger.debug("Found async backup job [id: {}, uuid: {}, vmId: {}] with " + @@ -2004,7 +1999,7 @@ public void scheduleBackups() { final Account backupAccount = accountService.getAccount(vm.getAccountId()); if (backupAccount == null || backupAccount.getState() == Account.State.DISABLED) { - logger.debug("Skip backup for VM ({}) since its account has been removed or disabled.", vm); + logger.debug("Skip backup for Instance ({}) since its account has been removed or disabled.", vm); continue; } @@ -2021,7 +2016,7 @@ public void scheduleBackups() { tmpBackupScheduleVO = backupScheduleDao.acquireInLockTable(backupScheduleId); final Long eventId = ActionEventUtils.onScheduledActionEvent(User.UID_SYSTEM, vm.getAccountId(), - EventTypes.EVENT_VM_BACKUP_CREATE, "creating backup for VM ID:" + vm.getUuid(), + EventTypes.EVENT_VM_BACKUP_CREATE, "creating Backup for Instance ID:" + vm.getUuid(), vmId, ApiCommandResourceType.VirtualMachine.toString(), true, 0); final Map params = new HashMap(); @@ -2085,7 +2080,7 @@ protected void runInContext() { private VMInstanceVO findVmById(final Long vmId) { final VMInstanceVO vm = vmInstanceDao.findById(vmId); if (vm == null) { - throw new CloudRuntimeException(String.format("Can't find any VM with ID: [%s].", vmId)); + throw new CloudRuntimeException(String.format("Can't find any Instance with ID: [%s].", vmId)); } return vm; } @@ -2269,7 +2264,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { backup.getBackupOfferingId(), backup.getAccountId(), backup.getDomainId(), backup.getZoneId())); ActionEventUtils.onCompletedActionEvent(User.UID_SYSTEM, vm.getAccountId(), EventVO.LEVEL_INFO, EventTypes.EVENT_VM_BACKUP_CREATE, - String.format("Created backup %s for VM ID: %s", backup.getUuid(), vm.getUuid()), + String.format("Created Backup %s for Instance ID: %s", backup.getUuid(), vm.getUuid()), vm.getId(), ApiCommandResourceType.VirtualMachine.toString(),0); } } diff --git a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java index 2b4e7ddc9d41..73ff79301fb7 100644 --- a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java @@ -22,12 +22,14 @@ import java.security.GeneralSecurityException; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.SecureRandom; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; @@ -39,6 +41,21 @@ import javax.naming.ConfigurationException; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import com.trilead.ssh2.Connection; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import com.cloud.host.HostVO; +import com.cloud.utils.PasswordGenerator; +import com.cloud.utils.ssh.SSHCmdHelper; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; +import org.apache.cloudstack.utils.security.KeyStoreUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ServerApiException; @@ -60,6 +77,7 @@ import org.joda.time.DateTimeZone; import com.cloud.agent.AgentManager; +import com.cloud.agent.api.routing.NetworkElementCommand; import com.cloud.alert.AlertManager; import com.cloud.certificate.CrlVO; import com.cloud.certificate.dao.CrlDao; @@ -81,6 +99,12 @@ public class CAManagerImpl extends ManagerBase implements CAManager { @Inject private HostDao hostDao; @Inject + private VMInstanceDao vmInstanceDao; + @Inject + private NetworkOrchestrationService networkOrchestrationService; + @Inject + private ConfigurationDao configDao; + @Inject private AgentManager agentManager; @Inject private BackgroundPollManager backgroundPollManager; @@ -177,12 +201,17 @@ public boolean revokeCertificate(final BigInteger certSerial, final String certC @Override @ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_PROVISION, eventDescription = "provisioning certificate for host", async = true) - public boolean provisionCertificate(final Host host, final Boolean reconnect, final String caProvider) { + public boolean provisionCertificate(final Host host, final Boolean reconnect, final String caProvider, final boolean forced) { if (host == null) { throw new CloudRuntimeException("Unable to find valid host to renew certificate for"); } CallContext.current().setEventDetails("Host ID: " + host.getUuid()); CallContext.current().putContextParameter(Host.class, host.getUuid()); + + if (forced) { + return provisionCertificateForced(host, reconnect, caProvider); + } + String csr = null; try { @@ -200,6 +229,141 @@ public boolean provisionCertificate(final Host host, final Boolean reconnect, fi } } + protected boolean provisionCertificateForced(Host host, Boolean reconnect, String caProvider) { + if (host.getType() == Host.Type.Routing && host.getHypervisorType() == com.cloud.hypervisor.Hypervisor.HypervisorType.KVM) { + return provisionKvmHostViaSsh(host, caProvider); + } else if (host.getType() == Host.Type.ConsoleProxy || host.getType() == Host.Type.SecondaryStorageVM) { + return provisionSystemVmViaSsh(host, reconnect, caProvider); + } + throw new CloudRuntimeException("Forced certificate provisioning is only supported for KVM hosts and SystemVMs."); + } + + @Override + public void provisionCertificateViaSsh(final Connection sshConnection, final String agentIp, final String agentHostname, final String caProvider) { + Integer validityPeriod = CAManager.CertValidityPeriod.value(); + if (validityPeriod < 1) { + validityPeriod = 1; + } + + String keystorePassword = PasswordGenerator.generateRandomPassword(16); + + // 1. Setup Keystore and Generate CSR + final SSHCmdHelper.SSHCmdResult keystoreSetupResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection, + String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + + "/etc/cloudstack/agent/agent.properties " + + "/etc/cloudstack/agent/%s " + + "%s %d " + + "/etc/cloudstack/agent/%s", + KeyStoreUtils.KS_SETUP_SCRIPT, + KeyStoreUtils.KS_FILENAME, + keystorePassword, + validityPeriod, + KeyStoreUtils.CSR_FILENAME)); + + if (!keystoreSetupResult.isSuccess()) { + throw new CloudRuntimeException("Failed to setup keystore and generate CSR via SSH on host: " + agentIp); + } + + // 2. Issue Certificate based on returned CSR + final String csr = keystoreSetupResult.getStdOut(); + final Certificate certificate = issueCertificate(csr, Arrays.asList(agentHostname, agentIp), + Collections.singletonList(agentIp), null, caProvider); + + if (certificate == null || certificate.getClientCertificate() == null) { + throw new CloudRuntimeException("Failed to issue certificates for host: " + agentIp); + } + + // 3. Import Certificate into agent keystore + final SetupCertificateCommand certificateCommand = new SetupCertificateCommand(certificate); + final SSHCmdHelper.SSHCmdResult setupCertResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection, + String.format("sudo /usr/share/cloudstack-common/scripts/util/%s " + + "/etc/cloudstack/agent/agent.properties %s " + + "/etc/cloudstack/agent/%s %s " + + "/etc/cloudstack/agent/%s \"%s\" " + + "/etc/cloudstack/agent/%s \"%s\" " + + "/etc/cloudstack/agent/%s \"%s\"", + KeyStoreUtils.KS_IMPORT_SCRIPT, + keystorePassword, + KeyStoreUtils.KS_FILENAME, + KeyStoreUtils.SSH_MODE, + KeyStoreUtils.CERT_FILENAME, + certificateCommand.getEncodedCertificate(), + KeyStoreUtils.CACERT_FILENAME, + certificateCommand.getEncodedCaCertificates(), + KeyStoreUtils.PKEY_FILENAME, + certificateCommand.getEncodedPrivateKey())); + + if (!setupCertResult.isSuccess()) { + throw new CloudRuntimeException("Failed to import certificates into agent keystore via SSH on host: " + agentIp); + } + } + + private boolean provisionKvmHostViaSsh(Host host, String caProvider) { + final HostVO hostVO = (HostVO) host; + hostDao.loadDetails(hostVO); + String username = hostVO.getDetail(ApiConstants.USERNAME); + String password = hostVO.getDetail(ApiConstants.PASSWORD); + String hostIp = host.getPrivateIpAddress(); + + int port = AgentManager.KVMHostDiscoverySshPort.valueIn(host.getClusterId()); + if (hostVO.getDetail(Host.HOST_SSH_PORT) != null) { + port = NumberUtils.toInt(hostVO.getDetail(Host.HOST_SSH_PORT), port); + } + + Connection sshConnection = null; + try { + sshConnection = new Connection(hostIp, port); + sshConnection.connect(null, 60000, 60000); + + String privateKey = configDao.getValue("ssh.privatekey"); + if (!SSHCmdHelper.acquireAuthorizedConnectionWithPublicKey(sshConnection, username, privateKey)) { + if (StringUtils.isEmpty(password) || !sshConnection.authenticateWithPassword(username, password)) { + throw new CloudRuntimeException("Failed to authenticate to host via SSH for forced provisioning: " + hostIp); + } + } + + provisionCertificateViaSsh(sshConnection, hostIp, host.getName(), caProvider); + + String sudoPrefix = "root".equals(username) ? "" : "sudo "; + SSHCmdHelper.sshExecuteCmd(sshConnection, sudoPrefix + "systemctl restart libvirtd"); + SSHCmdHelper.sshExecuteCmd(sshConnection, sudoPrefix + "systemctl restart cloudstack-agent"); + + return true; + } catch (Exception e) { + logger.error("Error during forced SSH provisioning for KVM host " + host.getUuid(), e); + return false; + } finally { + if (sshConnection != null) { + sshConnection.close(); + } + } + } + + private boolean provisionSystemVmViaSsh(Host host, Boolean reconnect, String caProvider) { + VMInstanceVO vm = vmInstanceDao.findVMByInstanceName(host.getName()); + if (vm == null) { + throw new CloudRuntimeException("Cannot find underlying VM for host: " + host.getName()); + } + + final Map sshAccessDetails = networkOrchestrationService.getSystemVMAccessDetails(vm); + final Map ipAddressDetails = new HashMap<>(sshAccessDetails); + ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME); + + try { + final Host hypervisorHost = hostDao.findById(vm.getHostId()); + if (hypervisorHost == null) { + throw new CloudRuntimeException("Cannot find hypervisor host for system VM: " + host.getName()); + } + + final Certificate certificate = issueCertificate(null, Arrays.asList(vm.getHostName(), vm.getInstanceName()), + new ArrayList<>(ipAddressDetails.values()), CertValidityPeriod.value(), caProvider); + return deployCertificate(hypervisorHost, certificate, reconnect, sshAccessDetails); + } catch (Exception e) { + logger.error("Failed to provision system VM " + host.getName() + " via hypervisor SSH proxy. Ensure the hypervisor host is connected.", e); + return false; + } + } + @Override public String generateKeyStoreAndCsr(final Host host, final Map sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException { final SetupKeyStoreCommand cmd = new SetupKeyStoreCommand(CertValidityPeriod.value()); @@ -211,11 +375,6 @@ public String generateKeyStoreAndCsr(final Host host, final Map return answer.getCsr(); } - private boolean isValidSystemVMType(Host.Type type) { - return Host.Type.SecondaryStorageVM.equals(type) || - Host.Type.ConsoleProxy.equals(type); - } - @Override public boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException { @@ -340,7 +499,7 @@ protected void runInContext() { if (AutomaticCertRenewal.valueIn(host.getClusterId())) { try { logger.debug("Attempting certificate auto-renewal for " + hostDescription, e); - boolean result = caManager.provisionCertificate(host, false, null); + boolean result = caManager.provisionCertificate(host, false, null, false); if (result) { logger.debug("Succeeded in auto-renewing certificate for " + hostDescription, e); } else { @@ -400,9 +559,57 @@ public boolean start() { logger.error("Failed to find valid configured CA provider, please check!"); return false; } + if (CaInjectDefaultTruststore.value()) { + injectCaCertIntoDefaultTruststore(); + } return true; } + private void injectCaCertIntoDefaultTruststore() { + try { + final List caCerts = configuredCaProvider.getCaCertificate(); + if (caCerts == null || caCerts.isEmpty()) { + logger.debug("No CA certificates found from the configured provider, skipping JVM truststore injection"); + return; + } + + final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + + // Copy existing default trusted certs + final TrustManagerFactory defaultTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + defaultTmf.init((KeyStore) null); + int aliasIndex = 0; + for (final TrustManager tm : defaultTmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + for (final X509Certificate cert : ((X509TrustManager) tm).getAcceptedIssuers()) { + trustStore.setCertificateEntry("default-ca-" + aliasIndex++, cert); + } + } + } + + // Add CA provider's certificates + int count = 0; + for (final X509Certificate caCert : caCerts) { + final String alias = "cloudstack-ca-" + count; + trustStore.setCertificateEntry(alias, caCert); + count++; + logger.info("Injected CA certificate into JVM default truststore: subject={}, alias={}", + caCert.getSubjectX500Principal().getName(), alias); + } + + // Reinitialize default SSLContext with the updated truststore + final TrustManagerFactory updatedTmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + updatedTmf.init(trustStore); + final SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, updatedTmf.getTrustManagers(), new SecureRandom()); + SSLContext.setDefault(sslContext); + logger.info("Successfully injected {} CA certificate(s) into JVM default truststore", count); + } catch (final GeneralSecurityException | IOException e) { + logger.error("Failed to inject CA certificate into JVM default truststore", e); + } + } + @Override public boolean configure(final String name, final Map params) throws ConfigurationException { backgroundPollManager.submitTask(new CABackgroundTask(this, hostDao)); @@ -433,7 +640,7 @@ public String getConfigComponentName() { public ConfigKey[] getConfigKeys() { return new ConfigKey[] {CAProviderPlugin, CertKeySize, CertSignatureAlgorithm, CertValidityPeriod, AutomaticCertRenewal, AllowHostIPInSysVMAgentCert, CABackgroundJobDelay, CertExpiryAlertPeriod, - CertManagementCustomSubjectAlternativeName + CertManagementCustomSubjectAlternativeName, CaInjectDefaultTruststore }; } diff --git a/server/src/main/java/org/apache/cloudstack/ha/HAManager.java b/server/src/main/java/org/apache/cloudstack/ha/HAManager.java index 8282c621c1e3..068230c6673d 100644 --- a/server/src/main/java/org/apache/cloudstack/ha/HAManager.java +++ b/server/src/main/java/org/apache/cloudstack/ha/HAManager.java @@ -67,11 +67,16 @@ public interface HAManager extends HAConfigManager { "The number of pending fence operations per management server. This setting determines the size of the size of the FENCE queue.", true); boolean transitionHAState(final HAConfig.Event event, final HAConfig haConfig); + HAProvider getHAProvider(final String name); + HAResourceCounter getHACounter(final Long resourceId, final HAResource.ResourceType resourceType); + void purgeHACounter(final Long resourceId, final HAResource.ResourceType resourceType); boolean isHAEligible(final HAResource resource); + Boolean isVMAliveOnHost(final Host host) throws Investigator.UnknownVM; - Status getHostStatus(final Host host); + + Status getHostStatusFromHAConfig(final Host host); } diff --git a/server/src/main/java/org/apache/cloudstack/ha/HAManagerImpl.java b/server/src/main/java/org/apache/cloudstack/ha/HAManagerImpl.java index a016be5c6e32..93b237ec20a9 100644 --- a/server/src/main/java/org/apache/cloudstack/ha/HAManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/ha/HAManagerImpl.java @@ -139,9 +139,7 @@ public synchronized HAResourceCounter getHACounter(final Long resourceId, final public synchronized void purgeHACounter(final Long resourceId, final HAResource.ResourceType resourceType) { final String key = resourceCounterKey(resourceId, resourceType); - if (haCounterMap.containsKey(key)) { - haCounterMap.remove(key); - } + haCounterMap.remove(key); } public boolean transitionHAState(final HAConfig.Event event, final HAConfig haConfig) { @@ -248,6 +246,7 @@ public boolean isHAEnabledForZone(final HAResource resource) { } private boolean isHAEnabledForCluster(final HAResource resource) { + // HA is enabled by default when cluster details doesn't exist if (resource == null || resource.getClusterId() == null) { return true; } @@ -259,14 +258,10 @@ private boolean isHAEligibleForResource(final HAResource resource) { if (resource == null || resource.getId() < 1L) { return false; } - HAResource.ResourceType resourceType = null; - if (resource instanceof Host) { - resourceType = HAResource.ResourceType.Host; - } - if (resourceType == null) { + if (!(resource instanceof Host)) { return false; } - final HAConfig haConfig = haConfigDao.findHAResource(resource.getId(), resourceType); + final HAConfig haConfig = haConfigDao.findHAResource(resource.getId(), HAResource.ResourceType.Host); return haConfig != null && haConfig.isEnabled() && haConfig.getState() != HAConfig.HAState.Disabled && haConfig.getState() != HAConfig.HAState.Ineligible; @@ -317,19 +312,23 @@ public Boolean isVMAliveOnHost(final Host host) throws Investigator.UnknownVM { throw new Investigator.UnknownVM(); } - public Status getHostStatus(final Host host) { + public Status getHostStatusFromHAConfig(final Host host) { final HAConfig haConfig = haConfigDao.findHAResource(host.getId(), HAResource.ResourceType.Host); - if (haConfig != null) { - if (haConfig.getState() == HAConfig.HAState.Fenced) { - logger.debug("HA: Agent [{}] is available/suspect/checking Up.", host); - return Status.Down; - } else if (haConfig.getState() == HAConfig.HAState.Degraded || haConfig.getState() == HAConfig.HAState.Recovering || haConfig.getState() == HAConfig.HAState.Fencing) { - logger.debug("HA: Agent [{}] is disconnected. State: {}, {}.", host, haConfig.getState(), haConfig.getState().getDescription()); - return Status.Disconnected; - } - return Status.Up; + if (haConfig == null) { + logger.warn("HA: Agent [{}] config is not available.", host); + return Status.Unknown; } - return Status.Unknown; + if (haConfig.getState() == HAConfig.HAState.Fenced) { + logger.debug("HA: Agent [{}] is fenced.", host); + return Status.Down; + } + if (haConfig.getState() == HAConfig.HAState.Degraded || haConfig.getState() == HAConfig.HAState.Recovering || haConfig.getState() == HAConfig.HAState.Fencing) { + logger.debug("HA: Agent [{}] is disconnected. State: {}, {}.", host, haConfig.getState(), haConfig.getState().getDescription()); + return Status.Disconnected; + } + + logger.debug("HA: Agent [{}] is considered Up (HA state can be Available/Suspect/Checking/Recovered). State: {}, {}.", host, haConfig.getState(), haConfig.getState().getDescription()); + return Status.Up; } ////////////////////////////////////////////////////// @@ -511,9 +510,14 @@ private boolean processHAStateChange(final HAConfig haConfig, final HAConfig.HAS // Attempt recovery if (newState == HAConfig.HAState.Recovering) { - if (counter.getRecoveryCounter() >= (Long) (haProvider.getConfigValue(HAProviderConfig.MaxRecoveryAttempts, resource))) { + long recoveryCounter = counter.getRecoveryCounter(); + Long maxRecoveryAttempts = (Long) (haProvider.getConfigValue(HAProviderConfig.MaxRecoveryAttempts, resource)); + if (recoveryCounter >= maxRecoveryAttempts) { + logger.debug("Recovery attempts have reached the configured limit: {} for the resource [{}].", maxRecoveryAttempts, resource); return false; } + + logger.debug("Recovery attempt #{} for the resource [{}]. Max recovery attempts configured is {}.", recoveryCounter + 1, resource, maxRecoveryAttempts); final RecoveryTask task = ComponentContext.inject(new RecoveryTask(resource, haProvider, haConfig, HAProviderConfig.RecoveryTimeout, recoveryExecutor)); final Future recoveryFuture = recoveryExecutor.submit(task); @@ -536,20 +540,20 @@ public boolean preStateTransitionEvent(final HAConfig.HAState oldState, final HA return false; } - logger.debug(String.format("HA state pre-transition:: new state=[%s], old state=[%s], for resource id=[%s], status=[%s], ha config state=[%s]." , newState, oldState, haConfig.getResourceId(), status, haConfig.getState())); + logger.debug("HA state pre-transition:: new state=[{}], old state=[{}], for resource id=[{}], status=[{}], ha config state=[{}].", newState, oldState, haConfig.getResourceId(), status, haConfig.getState()); if (status && haConfig.getState() != newState) { - logger.warn(String.format("HA state pre-transition:: HA state is not equal to transition state, HA state=[%s], new state=[%s].", haConfig.getState(), newState)); + logger.warn("HA state pre-transition:: HA state is not equal to transition state, HA state=[{}], new state=[{}].", haConfig.getState(), newState); } return processHAStateChange(haConfig, newState, status); } @Override public boolean postStateTransitionEvent(final StateMachine2.Transition transition, final HAConfig haConfig, final boolean status, final Object opaque) { - logger.debug(String.format("HA state post-transition:: new state=[%s], old state=[%s], for resource id=[%s], status=[%s], ha config state=[%s].", transition.getToState(), transition.getCurrentState(), haConfig.getResourceId(), status, haConfig.getState())); + logger.debug("HA state post-transition:: new state=[{}], old state=[{}], for resource id=[{}], status=[{}], ha config state=[{}].", transition.getToState(), transition.getCurrentState(), haConfig.getResourceId(), status, haConfig.getState()); if (status && haConfig.getState() != transition.getToState()) { - logger.warn(String.format("HA state post-transition:: HA state is not equal to transition state, HA state=[%s], new state=[%s].", haConfig.getState(), transition.getToState())); + logger.warn("HA state post-transition:: HA state is not equal to transition state, HA state=[{}], new state=[{}].", haConfig.getState(), transition.getToState()); } return processHAStateChange(haConfig, transition.getToState(), status); } @@ -645,7 +649,7 @@ protected void runInContext() { try { logger.debug("HA health check task is running..."); - final List haConfigList = new ArrayList(haConfigDao.listAll()); + final List haConfigList = new ArrayList<>(haConfigDao.listAll()); for (final HAConfig haConfig : haConfigList) { currentHaConfig = haConfig; @@ -676,8 +680,8 @@ protected void runInContext() { HAProviderConfig.HealthCheckTimeout, healthCheckExecutor)); healthCheckExecutor.submit(task); break; - default: - break; + default: + break; } final HAResourceCounter counter = getHACounter(haConfig.getResourceId(), haConfig.getResourceType()); @@ -695,16 +699,22 @@ protected void runInContext() { } if (haConfig.getState() == HAConfig.HAState.Recovering) { - if (counter.getRecoveryCounter() >= (Long) (haProvider.getConfigValue(HAProviderConfig.MaxRecoveryAttempts, resource))) { + long recoveryCounter = counter.getRecoveryCounter(); + Long maxRecoveryAttempts = (Long) (haProvider.getConfigValue(HAProviderConfig.MaxRecoveryAttempts, resource)); + if (recoveryCounter >= maxRecoveryAttempts) { + logger.debug("Recovery attempts have reached the max limit: {} for the resource [{}].", maxRecoveryAttempts, resource); transitionHAState(HAConfig.Event.RecoveryOperationThresholdExceeded, haConfig); } else { + logger.debug("Retry recovery for the resource [{}]. Max recovery attempts configured is {}.", resource, maxRecoveryAttempts); transitionHAState(HAConfig.Event.RetryRecovery, haConfig); } } if (haConfig.getState() == HAConfig.HAState.Recovered) { counter.markRecoveryStarted(); - if (counter.canExitRecovery((Long)(haProvider.getConfigValue(HAProviderConfig.RecoveryWaitTimeout, resource)))) { + Long recoveryWaitTimeout = (Long)(haProvider.getConfigValue(HAProviderConfig.RecoveryWaitTimeout, resource)); + logger.debug("Recovery started for the resource [{}], wait period configured to become Available is {} secs", resource, recoveryWaitTimeout); + if (counter.canExitRecovery(recoveryWaitTimeout)) { if (transitionHAState(HAConfig.Event.RecoveryWaitPeriodTimeout, haConfig)) { counter.markRecoveryCompleted(); } @@ -717,7 +727,7 @@ protected void runInContext() { } } catch (Throwable t) { if (currentHaConfig != null) { - logger.error(String.format("Error trying to perform health checks in HA manager [%s].", currentHaConfig.getHaProvider()), t); + logger.error("Error trying to perform health checks in HA manager [{}].", currentHaConfig.getHaProvider(), t); } else { logger.error("Error trying to perform health checks in HA manager.", t); } diff --git a/server/src/main/java/org/apache/cloudstack/ha/HAResourceCounter.java b/server/src/main/java/org/apache/cloudstack/ha/HAResourceCounter.java index f493f6926e07..23ebbaa6a7ae 100644 --- a/server/src/main/java/org/apache/cloudstack/ha/HAResourceCounter.java +++ b/server/src/main/java/org/apache/cloudstack/ha/HAResourceCounter.java @@ -36,6 +36,10 @@ public long getActivityCheckCounter() { return activityCheckCounter.get(); } + public long getActivityCheckFailureCounter() { + return activityCheckFailureCounter.get(); + } + public long getRecoveryCounter() { return recoveryOperationCounter.get(); } @@ -66,7 +70,7 @@ public synchronized void resetSuspectTimestamp() { firstHealthCheckFailureTimestamp = null; } - public boolean hasActivityThresholdExceeded(final double failureRatio) { + public boolean hasActivityFailureThresholdExceeded(final double failureRatio) { return activityCheckFailureCounter.get() > (activityCheckCounter.get() * failureRatio); } diff --git a/server/src/main/java/org/apache/cloudstack/ha/task/ActivityCheckTask.java b/server/src/main/java/org/apache/cloudstack/ha/task/ActivityCheckTask.java index 5ddbac626bc5..e27d039b3927 100644 --- a/server/src/main/java/org/apache/cloudstack/ha/task/ActivityCheckTask.java +++ b/server/src/main/java/org/apache/cloudstack/ha/task/ActivityCheckTask.java @@ -62,6 +62,8 @@ public synchronized void processResult(boolean result, Throwable t) { return; } + long activityCounter = counter.getActivityCheckCounter(); + logger.debug("Activity check #{}, result: {} for the resource {}. Max activity checks configured is {}", activityCounter + 1, result, getResource(), maxActivityChecks); counter.incrActivityCounter(!result); if (counter.getActivityCheckCounter() < maxActivityChecks) { @@ -69,7 +71,9 @@ public synchronized void processResult(boolean result, Throwable t) { return; } - if (counter.hasActivityThresholdExceeded(activityCheckFailureRatio)) { + long activityCheckFailureCount = counter.getActivityCheckFailureCounter(); + logger.debug("{} activity checks failed out of {} checks performed for the resource {}. Failure threshold configured is {}", activityCheckFailureCount, maxActivityChecks, getResource(), activityCheckFailureRatio); + if (counter.hasActivityFailureThresholdExceeded(activityCheckFailureRatio)) { haManager.transitionHAState(HAConfig.Event.ActivityCheckFailureOverThresholdRatio, haConfig); } else { if (haManager.transitionHAState(HAConfig.Event.ActivityCheckFailureUnderThresholdRatio, haConfig)) { diff --git a/server/src/main/java/org/apache/cloudstack/ha/task/BaseHATask.java b/server/src/main/java/org/apache/cloudstack/ha/task/BaseHATask.java index 6dc7b9281ba5..bf34d3514b62 100644 --- a/server/src/main/java/org/apache/cloudstack/ha/task/BaseHATask.java +++ b/server/src/main/java/org/apache/cloudstack/ha/task/BaseHATask.java @@ -97,7 +97,7 @@ public Boolean call() throws HACheckerException, HAFenceException, HARecoveryExc result = future.get(timeout, TimeUnit.SECONDS); } } catch (InterruptedException | ExecutionException e) { - logger.warn("Exception occurred while running " + getTaskType() + " on a resource: " + e.getMessage(), e.getCause()); + logger.warn("Exception occurred while running {} on a resource: {}", getTaskType(), e.getMessage(), e.getCause()); throwable = e.getCause(); } catch (TimeoutException e) { logger.trace("{} operation timed out for resource: {}", getTaskType(), resource); diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java index 4187586736d7..77cd7890e413 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/RouterDeploymentDefinition.java @@ -394,7 +394,7 @@ protected void executeDeployment() } } - protected void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { + public void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { sourceNatIp = null; DataCenter zone = dest.getDataCenter(); Long zoneId = null; @@ -548,4 +548,8 @@ protected boolean routersNeedReset() { return needReset; } + + public boolean getKeepMacAddressOnPublicNic() { + return guestNetwork == null || guestNetwork.getKeepMacAddressOnPublicNic(); + } } diff --git a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java index 063565ebf468..9e432265f2a1 100644 --- a/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java +++ b/server/src/main/java/org/apache/cloudstack/network/router/deployment/VpcRouterDeploymentDefinition.java @@ -117,7 +117,7 @@ protected boolean prepareDeployment() { } @Override - protected void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { + public void findSourceNatIP() throws InsufficientAddressCapacityException, ConcurrentOperationException { sourceNatIp = null; DataCenter zone = dest.getDataCenter(); Long zoneId = null; @@ -219,4 +219,9 @@ public boolean isRedundant() { public boolean isRollingRestart() { return vpc.isRollingRestart(); } + + @Override + public boolean getKeepMacAddressOnPublicNic() { + return vpc == null || vpc.getKeepMacAddressOnPublicNic(); + } } diff --git a/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml b/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml index d3781649845b..28b96b8d194b 100644 --- a/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml +++ b/server/src/main/resources/META-INF/cloudstack/server-allocator/spring-server-allocator-context.xml @@ -34,6 +34,9 @@ + + diff --git a/server/src/test/java/com/cloud/agent/manager/allocator/impl/BaseAllocatorTest.java b/server/src/test/java/com/cloud/agent/manager/allocator/impl/BaseAllocatorTest.java new file mode 100644 index 000000000000..ddd1e3959d51 --- /dev/null +++ b/server/src/test/java/com/cloud/agent/manager/allocator/impl/BaseAllocatorTest.java @@ -0,0 +1,219 @@ +// 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.agent.manager.allocator.impl; + +import com.cloud.capacity.CapacityManager; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.offering.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachineProfile; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class BaseAllocatorTest { + @Mock + HostDao hostDaoMock; + + @Mock + CapacityManager capacityManagerMock; + + @InjectMocks + @Spy + BaseAllocator baseAllocator = new MockBaseAllocator(); + + private final Host.Type type = Host.Type.Routing; + + private final Long clusterId = 1L; + + private final Long podId = 2L; + + private final Long dcId = 3L; + + private final HostVO host1 = Mockito.mock(HostVO.class); + + private final HostVO host2 = Mockito.mock(HostVO.class); + + private final HostVO host3 = Mockito.mock(HostVO.class); + + private final ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); + + private final String hostTag = "hostTag"; + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestHasServiceOfferingTagShouldRetainHostsWithServiceOfferingTag() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithMathingTags = new ArrayList<>(Arrays.asList(host1, host3)); + String hostTagOnTemplate = "hostTagOnTemplate"; + String hostTagOnOffering = null; + + Mockito.doReturn(hostsWithMathingTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate); + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestHasServiceOfferingTagAndHasHostTagOnTemplateShouldRetainHostsWithServiceOfferingTagAndTemplateTag() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithMathingServiceTags = new ArrayList<>(Arrays.asList(host1, host3)); + List hostsWithMathingTemplateTags = new ArrayList<>(Arrays.asList(host1, host2)); + String hostTagOnTemplate = "hostTagOnTemplate"; + String hostTagOnOffering = "hostTagOnOffering"; + + Mockito.doReturn(hostsWithMathingTemplateTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnTemplate); + Mockito.doReturn(hostsWithMathingServiceTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering); + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(1, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + } + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestHasHostTagOnTemplateShouldRetainHostsWithTemplateTag() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithMathingServiceTags = new ArrayList<>(Arrays.asList(host1, host3)); + String hostTagOnTemplate = null; + String hostTagOnOffering = "hostTagOnOffering"; + + Mockito.doReturn(hostsWithMathingServiceTags).when(hostDaoMock).listByHostTag(type, clusterId, podId, dcId, hostTagOnOffering); + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void retainHostsMatchingServiceOfferingAndTemplateTagsTestNoServiceTagAndNoTemplateTagShouldHaveAllSuitableHosts() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + String hostTagOnTemplate = null; + String hostTagOnOffering = null; + + baseAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(suitableHosts, type, dcId, podId, clusterId, hostTagOnOffering, hostTagOnTemplate); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void addHostsBasedOnTagRulesTestHostsWithTagRuleIsEmptyShouldNotAddToSuitableHosts() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List emptyList = new ArrayList<>(); + + Mockito.doReturn(emptyList).when(hostDaoMock).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.anyString()); + baseAllocator.addHostsBasedOnTagRules(hostTag, suitableHosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + } + + @Test + public void addHostsBasedOnTagRulesTestHostsWithTagRuleIsNotEmptyShouldAddToSuitableHosts() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsMatchingRuleTag = new ArrayList<>(Arrays.asList(host3)); + + Mockito.doReturn(hostsMatchingRuleTag).when(hostDaoMock).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.anyString()); + baseAllocator.addHostsBasedOnTagRules(hostTag, suitableHosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostHasCpuCapabilityAndCpuCapacityShouldReturnTrue() { + Boolean hasCpuCapability = true; + Boolean hasCpuCapacity = true; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertTrue(result); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostHasCpuCapabilityButNoCpuCapacityShouldReturnFalse() { + Boolean hasCpuCapability = true; + Boolean hasCpuCapacity = false; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertFalse(result); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostDoesNotHaveCpuCapabilityButHasCpuCapacityShouldReturnFalse() { + Boolean hasCpuCapability = false; + Boolean hasCpuCapacity = true; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertFalse(result); + } + + @Test + public void hostHasCpuCapabilityAndCapacityTestHostDoesNotHaveCpuCapabilityAndCpuCapacityShouldReturnFalse() { + Boolean hasCpuCapability = false; + Boolean hasCpuCapacity = false; + Pair pair = new Pair<>(hasCpuCapability, hasCpuCapacity); + + Mockito.doReturn(pair).when(capacityManagerMock).checkIfHostHasCpuCapabilityAndCapacity(Mockito.any(Host.class), Mockito.any(ServiceOffering.class), Mockito.anyBoolean()); + boolean result = baseAllocator.hostHasCpuCapabilityAndCapacity(true, serviceOffering, host1); + + Assert.assertFalse(result); + } + + class MockBaseAllocator extends BaseAllocator { + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Host.Type type, DeploymentPlanner.ExcludeList avoid, int returnUpTo) { + return null; + } + + @Override + public List allocateTo(VirtualMachineProfile vmProfile, DeploymentPlan plan, Host.Type type, DeploymentPlanner.ExcludeList avoid, List hosts, int returnUpTo, boolean considerReservedCapacity) { + return null; + } + } +} diff --git a/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java b/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java index 0d6a6fa8ff92..00cac5bbd52c 100644 --- a/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java +++ b/server/src/test/java/com/cloud/agent/manager/allocator/impl/FirstFitAllocatorTest.java @@ -18,214 +18,596 @@ package com.cloud.agent.manager.allocator.impl; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.cloud.agent.manager.allocator.HostAllocator; import com.cloud.capacity.CapacityManager; import com.cloud.capacity.CapacityVO; import com.cloud.deploy.DeploymentPlan; import com.cloud.deploy.DeploymentPlanner; import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; import com.cloud.offering.ServiceOffering; import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingDetailsVO; +import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDetailsDao; +import com.cloud.storage.VMTemplateVO; import com.cloud.user.Account; import com.cloud.utils.Pair; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VirtualMachineProfile; +import com.cloud.vm.dao.VMInstanceDetailsDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.junit.Assert; -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.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -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.anyMap; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) +public class FirstFitAllocatorTest { + private static final double TOLERANCE = 0.0001; + @Mock + HostDao hostDaoMock; -public class FirstFitAllocatorTest { - private static final double TOLERANCE = 0.0001; - private FirstFitAllocator allocator; - private CapacityManager capacityMgr; - private ServiceOfferingDetailsDao offeringDetailsDao; - private ResourceManager resourceMgr; - - private DeploymentPlan plan; - private ServiceOffering offering; - private DeploymentPlanner.ExcludeList avoid; - private Account account; - - private Host host1; - private Host host2; - private VirtualMachineProfile vmProfile; - ConfigurationDao configDao; - - @Before - public void setUp() { - allocator = new FirstFitAllocator(); - capacityMgr = mock(CapacityManager.class); - offeringDetailsDao = mock(ServiceOfferingDetailsDao.class); - resourceMgr = mock(ResourceManager.class); - configDao = mock(ConfigurationDao.class); - - allocator._capacityMgr = capacityMgr; - allocator._serviceOfferingDetailsDao = offeringDetailsDao; - allocator._resourceMgr = resourceMgr; - allocator._configDao = configDao; - - plan = mock(DeploymentPlan.class); - offering = mock(ServiceOffering.class); - avoid = mock(DeploymentPlanner.ExcludeList.class); - account = mock(Account.class); - - host1 = mock(Host.class); - host2 = mock(Host.class); - - vmProfile = mock(VirtualMachineProfile.class); - when(vmProfile.getId()).thenReturn(1L); - - when(plan.getDataCenterId()).thenReturn(1L); - when(offering.getCpu()).thenReturn(2); - when(offering.getSpeed()).thenReturn(1000); - when(offering.getRamSize()).thenReturn(2048); - when(offering.getId()).thenReturn(123L); - when(offering.getHostTag()).thenReturn(null); - when(offering.getVgpuProfileId()).thenReturn(null); - } - - @Test - public void testConfigure() throws Exception { - when(configDao.getConfiguration(anyMap())).thenReturn(new HashMap<>()); - assertTrue(allocator._checkHvm); - assertTrue(allocator.configure("test", new HashMap<>())); - } - - @Test - public void testAllocateTo_SuccessfulMatch() { - List inputHosts = Arrays.asList(host1, host2); - - // All hosts are allowed - when(avoid.shouldAvoid(host1)).thenReturn(false); - when(avoid.shouldAvoid(host2)).thenReturn(false); - - // No GPU requirement - when(offeringDetailsDao.findDetail(eq(123L), anyString())).thenReturn(null); - - // CPU capability and capacity is met - when(capacityMgr.checkIfHostReachMaxGuestLimit(any())).thenReturn(false); - when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(eq(host1), eq(offering), eq(true))) - .thenReturn(new Pair<>(true, true)); - when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(eq(host2), eq(offering), eq(true))) - .thenReturn(new Pair<>(true, false)); - - - when(resourceMgr.isGPUDeviceAvailable(offering, host1, vmProfile.getId())).thenReturn(true); - when(resourceMgr.isGPUDeviceAvailable(offering, host2, vmProfile.getId())).thenReturn(true); - - List result = allocator.allocateTo(vmProfile, plan, offering, null, avoid, inputHosts, 2, true, account); - - // Only host1 should be returned - assertEquals(1, result.size()); - assertTrue(result.contains(host1)); - assertFalse(result.contains(host2)); - } - - @Test - public void testAllocateTo_AvoidSetAndGuestLimit() { - List inputHosts = Arrays.asList(host1, host2); - - when(avoid.shouldAvoid(host1)).thenReturn(true); // Avoided - when(avoid.shouldAvoid(host2)).thenReturn(false); - - when(capacityMgr.checkIfHostReachMaxGuestLimit(host2)).thenReturn(true); // Reached limit - - List result = allocator.allocateTo(vmProfile, plan, offering, null, avoid, inputHosts, 2, true, account); - - assertTrue(result.isEmpty()); - } - - @Test - public void testAllocateTo_GPUNotAvailable() { - List inputHosts = Arrays.asList(host1); - when(avoid.shouldAvoid(host1)).thenReturn(false); - - // GPU required but not available - var vgpuDetail = mock(com.cloud.service.ServiceOfferingDetailsVO.class); - var pciDetail = mock(com.cloud.service.ServiceOfferingDetailsVO.class); - when(offeringDetailsDao.findDetail(eq(123L), eq("vgpuType"))).thenReturn(vgpuDetail); - when(offeringDetailsDao.findDetail(eq(123L), eq("pciDevice"))).thenReturn(pciDetail); - when(pciDetail.getValue()).thenReturn("NVIDIA"); - when(vgpuDetail.getValue()).thenReturn("GRID"); - - when(resourceMgr.isGPUDeviceAvailable(offering, host1, vmProfile.getId())).thenReturn(false); - - List result = allocator.allocateTo(vmProfile, plan, offering, null, avoid, inputHosts, 1, true, account); - - assertTrue(result.isEmpty()); - } - - @Test - public void testHostByCombinedCapacityOrder() { - // Test scenario 1: Default capacity usage (0.5 weight) - List mockCapacity = getHostCapacities(); - Map hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.5); - - // Verify host ordering and capacity values - Long firstHostId = hostByCombinedCapacity.keySet().iterator().next(); - Assert.assertEquals("Host with ID 1 should be first in ordering", Long.valueOf(1L), firstHostId); - Assert.assertEquals("Host 1 combined capacity should match expected value", - 0.9609375, hostByCombinedCapacity.get(1L), TOLERANCE); - Assert.assertEquals("Host 2 combined capacity should match expected value", - 0.9296875, hostByCombinedCapacity.get(2L), TOLERANCE); - - // Test scenario 2: Modified capacity usage (0.7 weight) - when(mockCapacity.get(0).getUsedCapacity()).thenReturn(1500L); - hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.7); - - // Verify new ordering after capacity change - firstHostId = hostByCombinedCapacity.keySet().iterator().next(); - Assert.assertEquals("Host with ID 2 should be first after capacity change", Long.valueOf(2L), firstHostId); - Assert.assertEquals("Host 2 combined capacity should match expected value after change", - 0.9515625, hostByCombinedCapacity.get(2L), TOLERANCE); - Assert.assertEquals("Host 1 combined capacity should match expected value after change", - 0.9484375, hostByCombinedCapacity.get(1L), TOLERANCE); - } - - List getHostCapacities() { - CapacityVO cpuCapacity1 = mock(CapacityVO.class); - when(cpuCapacity1.getHostOrPoolId()).thenReturn(1L); - when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); - when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); - when(cpuCapacity1.getUsedCapacity()).thenReturn(500L); - when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); - - CapacityVO cpuCapacity2 = mock(CapacityVO.class); - when(cpuCapacity2.getHostOrPoolId()).thenReturn(2L); - when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); - when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); - when(cpuCapacity2.getUsedCapacity()).thenReturn(500L); - when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); - - CapacityVO memCapacity1 = mock(CapacityVO.class); - when(memCapacity1.getHostOrPoolId()).thenReturn(1L); - when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); - when(memCapacity1.getReservedCapacity()).thenReturn(0L); - when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); - when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); - - CapacityVO memCapacity2 = mock(CapacityVO.class); - when(memCapacity2.getHostOrPoolId()).thenReturn(2L); - when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); - when(memCapacity2.getReservedCapacity()).thenReturn(0L); - when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); - when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); - return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); - } + @Mock + ResourceManager resourceManagerMock; + + @Mock + VMInstanceDetailsDao userVmDetailsDaoMock; + + @Mock + ServiceOfferingDetailsDao serviceOfferingDetailsDao; + + @Mock + CapacityManager capacityMgr; + + @Mock + ConfigurationDao configDao; + + @Spy + @InjectMocks + FirstFitAllocator firstFitAllocatorSpy; + + private final Host.Type type = Host.Type.Routing; + + private final Long clusterId = 1L; + + private final Long podId = 2L; + + private final Long dcId = 3L; + + private final List emptyList = new ArrayList<>(); + + private final String hostTag = "hostTag"; + + private final String templateTag = "templateTag"; + + private final HostVO host1 = mock(HostVO.class); + + private final HostVO host2 = mock(HostVO.class); + + private final HostVO host3 = mock(HostVO.class); + + private final ServiceOfferingVO serviceOffering = mock(ServiceOfferingVO.class); + + private final DeploymentPlanner.ExcludeList excludeList = mock(DeploymentPlanner.ExcludeList.class); + + private final VirtualMachineProfile virtualMachineProfile = mock(VirtualMachineProfile.class); + + private final VMTemplateVO vmTemplateVO = mock(VMTemplateVO.class); + + private final Account account = mock(Account.class); + + private final DeploymentPlan deploymentPlan = mock(DeploymentPlan.class); + + private final DeploymentPlanner.ExcludeList avoid = mock(DeploymentPlanner.ExcludeList.class); + + private final ServiceOffering offering = mock(ServiceOffering.class); + + private final boolean considerReservedCapacity = true; + + @Test + public void testConfigure() throws Exception { + when(configDao.getConfiguration(Mockito.anyMap())).thenReturn(new HashMap<>()); + Assert.assertTrue(firstFitAllocatorSpy._checkHvm); + Assert.assertTrue(firstFitAllocatorSpy.configure("test", new HashMap<>())); + } + + @Test + public void testAllocateTo_SuccessfulMatch() { + List inputHosts = Arrays.asList(host1, host2); + + // All hosts are allowed + when(avoid.shouldAvoid(host1)).thenReturn(false); + when(avoid.shouldAvoid(host2)).thenReturn(false); + + // CPU capability and capacity is met + when(capacityMgr.checkIfHostReachMaxGuestLimit(Mockito.any(Host.class))).thenReturn(false); + when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(host1, offering, true)) + .thenReturn(new Pair<>(true, true)); + when(capacityMgr.checkIfHostHasCpuCapabilityAndCapacity(host2, offering,true)) + .thenReturn(new Pair<>(true, false)); + + when(resourceManagerMock.isGPUDeviceAvailable(offering, host1, virtualMachineProfile.getId())).thenReturn(true); + when(resourceManagerMock.isGPUDeviceAvailable(offering, host2, virtualMachineProfile.getId())).thenReturn(true); + + List result = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, offering, null, avoid, inputHosts, 2, true, account); + + // Only host1 should be returned + Assert.assertEquals(1, result.size()); + Assert.assertTrue(result.contains(host1)); + Assert.assertFalse(result.contains(host2)); + } + + @Test + public void testAllocateTo_AvoidSetAndGuestLimit() { + List inputHosts = Arrays.asList(host1, host2); + + when(avoid.shouldAvoid(host1)).thenReturn(true); // Avoided + when(avoid.shouldAvoid(host2)).thenReturn(false); + + when(capacityMgr.checkIfHostReachMaxGuestLimit(host2)).thenReturn(true); // Reached limit + + List result = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, offering, null, avoid, inputHosts, 2, true, account); + + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void testAllocateTo_GPUNotAvailable() { + List inputHosts = Arrays.asList(host1); + when(avoid.shouldAvoid(host1)).thenReturn(false); + when(resourceManagerMock.isGPUDeviceAvailable(offering, host1, virtualMachineProfile.getId())).thenReturn(false); + + List result = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, offering, null, avoid, inputHosts, 1, true, account); + + Assert.assertTrue(result.isEmpty()); + } + + @Test + public void allocateToTestHostTypeStorageShouldReturnNull() { + List suitableHosts = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, Host.Type.Storage, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void allocateToTestSuitableHostsEmptyShouldReturnNull() { + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(account).when(virtualMachineProfile).getOwner(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(templateTag).when(vmTemplateVO).getTemplateTag(); + Mockito.doReturn(emptyList).when(firstFitAllocatorSpy).retrieveHosts(Mockito.any(VirtualMachineProfile.class), Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + List suitableHosts = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void allocateToTestSuitableHostsNotEmptyShouldCallAllocateToMethod() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(account).when(virtualMachineProfile).getOwner(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(templateTag).when(vmTemplateVO).getTemplateTag(); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).retrieveHosts(Mockito.any(VirtualMachineProfile.class), Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).allocateTo(Mockito.any(VirtualMachineProfile.class), Mockito.any(DeploymentPlan.class), Mockito.any(ServiceOffering.class), Mockito.any(VMTemplateVO.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyList(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.any(Account.class)); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsToAvoidSet(Mockito.any(Host.Type.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + List suitableHosts = firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Mockito.verify(firstFitAllocatorSpy, Mockito.times(1)).allocateTo(virtualMachineProfile, deploymentPlan, serviceOffering, vmTemplateVO, excludeList, hosts, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity, account); + Assert.assertEquals(2, suitableHosts.size()); + } + + @Test + public void allocateToTestProvidedHostsNotNullShouldCallAddHostsToAvoidSetMethod() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(account).when(virtualMachineProfile).getOwner(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(templateTag).when(vmTemplateVO).getTemplateTag(); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).retrieveHosts(Mockito.any(VirtualMachineProfile.class), Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + Mockito.doReturn(hosts).when(firstFitAllocatorSpy).allocateTo(Mockito.any(VirtualMachineProfile.class), Mockito.any(DeploymentPlan.class), Mockito.any(ServiceOffering.class), Mockito.any(VMTemplateVO.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyList(), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.any(Account.class)); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsToAvoidSet(Mockito.any(Host.Type.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + firstFitAllocatorSpy.allocateTo(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Mockito.verify(firstFitAllocatorSpy, Mockito.times(1)).addHostsToAvoidSet(Mockito.any(Host.Type.class), Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNullAndHaTagNotNullShouldReturnOnlyHostsWithHaTag() { + List allUpAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithHaTag = new ArrayList<>(Arrays.asList(host1, host2)); + String hostVmTag = "haVmTag"; + + Mockito.doReturn(hostVmTag).when(virtualMachineProfile).getParameter(Mockito.any(VirtualMachineProfile.Param.class)); + Mockito.doReturn(allUpAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithHaTag).when(hostDaoMock).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.anyString(), Mockito.anyList()); + List resultHosts = firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, emptyList, clusterId, podId, dcId, hostTag, templateTag); + + Assert.assertEquals(2, resultHosts.size()); + Assert.assertEquals(host1, resultHosts.get(0)); + Assert.assertEquals(host2, resultHosts.get(1)); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNotNullAndHaTagNotNullShouldReturnOnlyHostsToFilterWithHaTag() { + List hostsToFilter = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List hostsWithHaTag = new ArrayList<>(Arrays.asList(host1, host2)); + String hostVmTag = "haVmTag"; + + Mockito.doReturn(hostVmTag).when(virtualMachineProfile).getParameter(Mockito.any(VirtualMachineProfile.Param.class)); + Mockito.doReturn(hostsWithHaTag).when(hostDaoMock).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.anyString(), Mockito.anyList()); + List resultHosts = firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, hostsToFilter, clusterId, podId, dcId, hostTag, templateTag); + + Assert.assertEquals(2, resultHosts.size()); + Assert.assertEquals(host1, resultHosts.get(0)); + Assert.assertEquals(host2, resultHosts.get(1)); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNullAndNoHaTagAndNoHostTagShouldReturnOnlyAllUpAndEnabledNonHaHosts() { + List allUpAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List upAndEnabledHostsWithNoHa = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(allUpAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(upAndEnabledHostsWithNoHa).when(resourceManagerMock).listAllUpAndEnabledNonHAHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.nullable(String.class), Mockito.anyList()); + List resultHosts = firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, emptyList, clusterId, podId, dcId, null, null); + + Assert.assertEquals(2, resultHosts.size()); + Assert.assertEquals(host1, resultHosts.get(0)); + Assert.assertEquals(host2, resultHosts.get(1)); + } + + @Test + public void retrieveHostsTestHostsToFilterIsNullAndNoHaTagWithHostTagShouldCallRetainHostsMatchingServiceOfferingAndTemplateTags() { + List allUpAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(allUpAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doNothing().when(firstFitAllocatorSpy).retainHostsMatchingServiceOfferingAndTemplateTags(Mockito.anyList(), Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + Mockito.doNothing().when(firstFitAllocatorSpy).filterHostsWithUefiEnabled(Mockito.any(Host.Type.class), Mockito.any(VirtualMachineProfile.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyList()); + Mockito.doNothing().when(firstFitAllocatorSpy).addHostsBasedOnTagRules(Mockito.anyString(), Mockito.anyList()); + firstFitAllocatorSpy.retrieveHosts(virtualMachineProfile, type, emptyList, clusterId, podId, dcId, hostTag, templateTag); + + Mockito.verify(firstFitAllocatorSpy, Mockito.times(1)).retainHostsMatchingServiceOfferingAndTemplateTags(Mockito.anyList(), Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void addHostsToAvoidSetTestAllHostsWereConsideredForAllocationShouldNotAddAnyHostToTheAvoidSet() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(suitableHosts).when(hostDaoMock).listAllUpAndEnabledNonHAHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.nullable(String.class)); + firstFitAllocatorSpy.addHostsToAvoidSet(type, excludeList, clusterId, podId, dcId, suitableHosts); + + Assert.assertTrue(excludeList.getHostsToAvoid().isEmpty()); + } + + @Test + public void addHostsToAvoidSetTestNotAllHostsWereConsideredForAllocationShouldAddHostToTheAvoidSet() { + List allUpAndEnabledNonHAHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List consideredHosts = new ArrayList<>(Arrays.asList(host2, host3)); + + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doCallRealMethod().when(excludeList).addHost(Mockito.anyLong()); + Mockito.doCallRealMethod().when(excludeList).getHostsToAvoid(); + Mockito.doReturn(allUpAndEnabledNonHAHosts).when(hostDaoMock).listAllUpAndEnabledNonHAHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.nullable(String.class)); + firstFitAllocatorSpy.addHostsToAvoidSet(type, excludeList, clusterId, podId, dcId, consideredHosts); + + Assert.assertEquals(1, excludeList.getHostsToAvoid().size()); + Assert.assertTrue(excludeList.getHostsToAvoid().contains(1L)); + } + + @Test + public void filterHostsWithUefiEnabledTestNoDetailWithUefiShouldNotFilterAnyHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + VMInstanceDetailVO userVmDetailVO = null; + + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void filterHostsWithUefiEnabledTestDetailWithUefiWithInvalidModeShouldNotFilterAnyHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + VMInstanceDetailVO userVmDetailVO = mock(VMInstanceDetailVO.class); + String bootMode = "Invalid mode"; + + Mockito.doReturn(bootMode).when(userVmDetailVO).getValue(); + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void filterHostsWithUefiEnabledTestDetailWithUefiWithLegacyModeShouldFilterHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List uefiHosts = new ArrayList<>(Arrays.asList(host2, host3)); + VMInstanceDetailVO userVmDetailVO = mock(VMInstanceDetailVO.class); + String bootMode = ApiConstants.BootMode.LEGACY.toString(); + + Mockito.doReturn(bootMode).when(userVmDetailVO).getValue(); + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(uefiHosts).when(hostDaoMock).listByHostCapability(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void filterHostsWithUefiEnabledTestDetailWithUefiWithSecureModeShouldFilterHost() { + List suitableHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List uefiHosts = new ArrayList<>(Arrays.asList(host2, host3)); + VMInstanceDetailVO userVmDetailVO = mock(VMInstanceDetailVO.class); + String bootMode = ApiConstants.BootMode.SECURE.toString(); + + Mockito.doReturn(bootMode).when(userVmDetailVO).getValue(); + Mockito.doReturn(userVmDetailVO).when(userVmDetailsDaoMock).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(uefiHosts).when(hostDaoMock).listByHostCapability(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.filterHostsWithUefiEnabled(type, virtualMachineProfile, clusterId, podId, dcId, suitableHosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void offeringRequestedVGpuAndHostDoesNotHaveItTestVGpuRequestedButHostDoesNotHaveItShouldReturnTrue() { + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doCallRealMethod().when(excludeList).addHost(Mockito.anyLong()); + Mockito.doCallRealMethod().when(excludeList).getHostsToAvoid(); + Mockito.doReturn(false).when(resourceManagerMock).isGPUDeviceAvailable(Mockito.any(ServiceOffering.class), Mockito.any(Host.class), Mockito.any(Long.class)); + boolean result = firstFitAllocatorSpy.offeringRequestedVGpuAndHostDoesNotHaveIt(serviceOffering, virtualMachineProfile, excludeList, host1); + + Assert.assertTrue(result); + Assert.assertEquals(1, excludeList.getHostsToAvoid().size()); + Assert.assertTrue(excludeList.getHostsToAvoid().contains(1L)); + } + + @Test + public void offeringRequestedVGpuAndHostDoesNotHaveItTestVGpuRequestedAndHostDoesHaveItShouldReturnFalse() { + Mockito.doReturn(true).when(resourceManagerMock).isGPUDeviceAvailable(Mockito.any(ServiceOffering.class), Mockito.any(Host.class), Mockito.any(Long.class)); + boolean result = firstFitAllocatorSpy.offeringRequestedVGpuAndHostDoesNotHaveIt(serviceOffering, virtualMachineProfile, excludeList, host1); + + Assert.assertFalse(result); + Mockito.verify(excludeList, Mockito.never()).addHost(Mockito.anyLong()); + } + + @Test + public void filterHostWithNoHvmIfTemplateRequestedTestTemplateDoesNotRequireHvm() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(false).when(vmTemplateVO).isRequiresHvm(); + List suitableHosts = firstFitAllocatorSpy.filterHostWithNoHvmIfTemplateRequested(vmTemplateVO, hosts); + + Assert.assertEquals(3, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host2, suitableHosts.get(1)); + Assert.assertEquals(host3, suitableHosts.get(2)); + } + + @Test + public void filterHostWithNoHvmIfTemplateRequestedTestTemplateRequiresHvmShouldReturnOnlyHvmHosts() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(true).when(vmTemplateVO).isRequiresHvm(); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host1); + Mockito.doReturn(false).when(firstFitAllocatorSpy).hostSupportsHVM(host2); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host3); + List suitableHosts = firstFitAllocatorSpy.filterHostWithNoHvmIfTemplateRequested(vmTemplateVO, hosts); + + Assert.assertEquals(2, suitableHosts.size()); + Assert.assertEquals(host1, suitableHosts.get(0)); + Assert.assertEquals(host3, suitableHosts.get(1)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingRequestedVGpuViaDetailShouldDoNothing() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + ServiceOfferingDetailsVO requestedVGpuType = mock(ServiceOfferingDetailsVO.class); + + Mockito.doReturn(requestedVGpuType).when(serviceOfferingDetailsDao).findDetail(Mockito.anyLong(), Mockito.anyString()); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, hosts); + + Assert.assertEquals(3, hosts.size()); + Assert.assertEquals(host1, hosts.get(0)); + Assert.assertEquals(host2, hosts.get(1)); + Assert.assertEquals(host3, hosts.get(2)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingRequestedVGpuViaProfileIdShouldDoNothing() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(1L).when(serviceOffering).getVgpuProfileId(); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, hosts); + + Assert.assertEquals(3, hosts.size()); + Assert.assertEquals(host1, hosts.get(0)); + Assert.assertEquals(host2, hosts.get(1)); + Assert.assertEquals(host3, hosts.get(2)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingDidNotRequestVGpuShouldReorderList() { + List allHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(null).when(serviceOfferingDetailsDao).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(null).when(serviceOffering).getVgpuProfileId(); + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doReturn(2L).when(host2).getId(); + Mockito.doReturn(3L).when(host3).getId(); + Mockito.doReturn(true).when(resourceManagerMock).isHostGpuEnabled(1L); + Mockito.doReturn(false).when(resourceManagerMock).isHostGpuEnabled(2L); + Mockito.doReturn(false).when(resourceManagerMock).isHostGpuEnabled(3L); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, allHosts); + + Assert.assertEquals(3, allHosts.size()); + Assert.assertEquals(host2, allHosts.get(0)); + Assert.assertEquals(host3, allHosts.get(1)); + Assert.assertEquals(host1, allHosts.get(2)); + } + + @Test + public void prioritizeHostsByGpuEnabledTestServiceOfferingDidNotRequestVGpuShouldNotReorderListIfThereIsNoHostWithVGpu() { + List allHosts = new ArrayList<>(Arrays.asList(host1, host2, host3)); + + Mockito.doReturn(null).when(serviceOfferingDetailsDao).findDetail(Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(null).when(serviceOffering).getVgpuProfileId(); + Mockito.doReturn(1L).when(host1).getId(); + Mockito.doReturn(2L).when(host2).getId(); + Mockito.doReturn(3L).when(host3).getId(); + Mockito.doReturn(false).when(resourceManagerMock).isHostGpuEnabled(Mockito.anyLong()); + firstFitAllocatorSpy.prioritizeHostsByGpuEnabled(serviceOffering, allHosts); + + Assert.assertEquals(3, allHosts.size()); + Assert.assertEquals(host1, allHosts.get(0)); + Assert.assertEquals(host2, allHosts.get(1)); + Assert.assertEquals(host3, allHosts.get(2)); + } + + @Test + public void prioritizeHostsByHvmCapabilityTestTemplateDidNotRequestedHvmShouldPutHostThatDoesNotSupportHvmInStartOfThePriorityList() { + List hostsToCheck = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List prioritizedHosts = new ArrayList<>(); + + Mockito.doReturn(false).when(vmTemplateVO).isRequiresHvm(); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host1); + Mockito.doReturn(false).when(firstFitAllocatorSpy).hostSupportsHVM(host2); + Mockito.doReturn(true).when(firstFitAllocatorSpy).hostSupportsHVM(host3); + firstFitAllocatorSpy.prioritizeHostsByHvmCapability(vmTemplateVO, hostsToCheck, prioritizedHosts); + + Assert.assertEquals(3, prioritizedHosts.size()); + Assert.assertEquals(host2, prioritizedHosts.get(0)); + Assert.assertEquals(host1, prioritizedHosts.get(1)); + Assert.assertEquals(host3, prioritizedHosts.get(2)); + } + + @Test + public void prioritizeHostsByHvmCapabilityTestTemplateRequiresHvmShouldNotReorderList() { + List hostsToCheck = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List prioritizedHosts = new ArrayList<>(); + + Mockito.doReturn(true).when(vmTemplateVO).isRequiresHvm(); + firstFitAllocatorSpy.prioritizeHostsByHvmCapability(vmTemplateVO, hostsToCheck, prioritizedHosts); + + Assert.assertEquals(3, prioritizedHosts.size()); + Assert.assertEquals(host1, prioritizedHosts.get(0)); + Assert.assertEquals(host2, prioritizedHosts.get(1)); + Assert.assertEquals(host3, prioritizedHosts.get(2)); + } + + @Test + public void prioritizeHostsWithMatchingGuestOsTestShouldPutMatchingHostInHighPriorityAndHostsThatDoesNotMatchInLowPriorityList() { + List hostsToCheck = new ArrayList<>(Arrays.asList(host1, host2, host3)); + List highPriorityHosts = new ArrayList<>(); + List lowPriorityHosts = new ArrayList<>(); + String guestOsCategory1 = "guestOsCategory1"; + String guestOsCategory2 = "guestOsCategory2"; + + Mockito.doReturn(guestOsCategory1).when(firstFitAllocatorSpy).getTemplateGuestOSCategory(vmTemplateVO); + Mockito.doReturn(guestOsCategory1).when(firstFitAllocatorSpy).getHostGuestOSCategory(host1); + Mockito.doReturn(guestOsCategory2).when(firstFitAllocatorSpy).getHostGuestOSCategory(host2); + Mockito.doReturn(null).when(firstFitAllocatorSpy).getHostGuestOSCategory(host3); + firstFitAllocatorSpy.prioritizeHostsWithMatchingGuestOs(vmTemplateVO,hostsToCheck, highPriorityHosts, lowPriorityHosts); + + Assert.assertEquals(1, highPriorityHosts.size()); + Assert.assertEquals(host1, highPriorityHosts.get(0)); + Assert.assertEquals(1, lowPriorityHosts.size()); + Assert.assertEquals(host2, lowPriorityHosts.get(0)); + } + + @Test + public void testHostByCombinedCapacityOrder() { + // Test scenario 1: Default capacity usage (0.5 weight) + List mockCapacity = getHostCapacities(); + Map hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.5); + + // Verify host ordering and capacity values + Long firstHostId = hostByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Host with ID 1 should be first in ordering", Long.valueOf(1L), firstHostId); + Assert.assertEquals("Host 1 combined capacity should match expected value", + 0.9609375, hostByCombinedCapacity.get(1L), TOLERANCE); + Assert.assertEquals("Host 2 combined capacity should match expected value", + 0.9296875, hostByCombinedCapacity.get(2L), TOLERANCE); + + // Test scenario 2: Modified capacity usage (0.7 weight) + when(mockCapacity.get(0).getUsedCapacity()).thenReturn(1500L); + hostByCombinedCapacity = FirstFitAllocator.getHostByCombinedCapacities(mockCapacity, 0.7); + + // Verify new ordering after capacity change + firstHostId = hostByCombinedCapacity.keySet().iterator().next(); + Assert.assertEquals("Host with ID 2 should be first after capacity change", Long.valueOf(2L), firstHostId); + Assert.assertEquals("Host 2 combined capacity should match expected value after change", + 0.9515625, hostByCombinedCapacity.get(2L), TOLERANCE); + Assert.assertEquals("Host 1 combined capacity should match expected value after change", + 0.9484375, hostByCombinedCapacity.get(1L), TOLERANCE); + } + + List getHostCapacities() { + CapacityVO cpuCapacity1 = mock(CapacityVO.class); + when(cpuCapacity1.getHostOrPoolId()).thenReturn(1L); + when(cpuCapacity1.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity1.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity1.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO cpuCapacity2 = mock(CapacityVO.class); + when(cpuCapacity2.getHostOrPoolId()).thenReturn(2L); + when(cpuCapacity2.getTotalCapacity()).thenReturn(32000L); + when(cpuCapacity2.getReservedCapacity()).thenReturn(0L); + when(cpuCapacity2.getUsedCapacity()).thenReturn(500L); + when(cpuCapacity2.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_CPU); + + CapacityVO memCapacity1 = mock(CapacityVO.class); + when(memCapacity1.getHostOrPoolId()).thenReturn(1L); + when(memCapacity1.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity1.getReservedCapacity()).thenReturn(0L); + when(memCapacity1.getUsedCapacity()).thenReturn(536870912L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + + CapacityVO memCapacity2 = mock(CapacityVO.class); + when(memCapacity2.getHostOrPoolId()).thenReturn(2L); + when(memCapacity2.getTotalCapacity()).thenReturn(8589934592L); + when(memCapacity2.getReservedCapacity()).thenReturn(0L); + when(memCapacity2.getUsedCapacity()).thenReturn(1073741824L); + when(memCapacity1.getCapacityType()).thenReturn(CapacityVO.CAPACITY_TYPE_MEMORY); + return Arrays.asList(cpuCapacity1, memCapacity1, cpuCapacity2, memCapacity2); + } } diff --git a/server/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java b/server/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java new file mode 100644 index 000000000000..ab9b322eb843 --- /dev/null +++ b/server/src/test/java/com/cloud/agent/manager/allocator/impl/RandomAllocatorTest.java @@ -0,0 +1,332 @@ +// 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.agent.manager.allocator.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.offering.ServiceOffering; +import com.cloud.resource.ResourceManager; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.vm.VirtualMachineProfile; +import org.apache.commons.collections.CollectionUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; + +@RunWith(MockitoJUnitRunner.class) +public class RandomAllocatorTest { + + @Mock + HostDao hostDao; + + @Spy + @InjectMocks + RandomAllocator randomAllocator; + + @Mock + ResourceManager resourceManagerMock; + + private final Host.Type type = Host.Type.Routing; + + private final Long clusterId = 1L; + + private final Long podId = 2L; + + private final Long zoneId = 3L; + + private final List emptyList = new ArrayList<>(); + + private final String hostTag = "hostTag"; + + private final HostVO host1 = Mockito.mock(HostVO.class); + + private final HostVO host2 = Mockito.mock(HostVO.class); + + private final HostVO host3 = Mockito.mock(HostVO.class); + + private final VMTemplateVO vmTemplateVO = Mockito.mock(VMTemplateVO.class); + + private final ServiceOfferingVO serviceOffering = Mockito.mock(ServiceOfferingVO.class); + + private final DeploymentPlanner.ExcludeList excludeList = Mockito.mock(DeploymentPlanner.ExcludeList.class); + + private final VirtualMachineProfile virtualMachineProfile = Mockito.mock(VirtualMachineProfile.class); + + private final DeploymentPlan deploymentPlan = Mockito.mock(DeploymentPlan.class); + + private final boolean considerReservedCapacity = true; + + + @Test + public void testListHostsByTags() { + Host.Type type = Host.Type.Routing; + Long id = 1L; + String templateTag = "tag1"; + String offeringTag = "tag2"; + HostVO host1 = Mockito.mock(HostVO.class); + HostVO host2 = Mockito.mock(HostVO.class); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, offeringTag)).thenReturn(List.of(host1, host2)); + + // No template tagged host + ArrayList noTemplateTaggedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, templateTag)).thenReturn(new ArrayList<>()); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(noTemplateTaggedHosts, type, zoneId, podId, clusterId, offeringTag, templateTag); + Assert.assertTrue(CollectionUtils.isEmpty(noTemplateTaggedHosts)); + + // Different template tagged host + ArrayList differentTemplateTaggedHost = new ArrayList<>(Arrays.asList(host1, host2)); + HostVO host3 = Mockito.mock(HostVO.class); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, templateTag)).thenReturn(List.of(host3)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(differentTemplateTaggedHost, type, zoneId, podId, clusterId, offeringTag, templateTag); + Assert.assertTrue(CollectionUtils.isEmpty(differentTemplateTaggedHost)); + + // Matching template tagged host + ArrayList matchingTemplateTaggedHost = new ArrayList<>(Arrays.asList(host1, host2)); + Mockito.when(hostDao.listByHostTag(type, clusterId, podId, zoneId, templateTag)).thenReturn(List.of(host1)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(matchingTemplateTaggedHost, type, zoneId, podId, clusterId, offeringTag, templateTag); + Assert.assertFalse(CollectionUtils.isEmpty(matchingTemplateTaggedHost)); + Assert.assertEquals(1, matchingTemplateTaggedHost.size()); + + // No template tag + ArrayList noTemplateTag = new ArrayList<>(Arrays.asList(host1, host2)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(noTemplateTag, type, zoneId, podId, clusterId, offeringTag, null); + Assert.assertFalse(CollectionUtils.isEmpty(noTemplateTag)); + Assert.assertEquals(2, noTemplateTag.size()); + + // No offering tag + ArrayList noOfferingTag = new ArrayList<>(Arrays.asList(host1, host2)); + randomAllocator.retainHostsMatchingServiceOfferingAndTemplateTags(noOfferingTag, type, zoneId, podId, clusterId, null, templateTag); + Assert.assertFalse(CollectionUtils.isEmpty(noOfferingTag)); + Assert.assertEquals(1, noOfferingTag.size()); + } + + @Test + public void findSuitableHostsTestHostTypeStorageShouldReturnNull() { + List suitableHosts = randomAllocator.findSuitableHosts(virtualMachineProfile, deploymentPlan, Host.Type.Storage, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void findSuitableHostsTestNoAvailableHostsShouldReturnNull() { + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(emptyList).when(randomAllocator).retrieveHosts(Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.any(VMTemplateVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + List suitableHosts = randomAllocator.findSuitableHosts(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Assert.assertNull(suitableHosts); + } + + @Test + public void findSuitableHostsTestAvailableHostsShouldCallFilterAvailableHostsOnce() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + + Mockito.doReturn(serviceOffering).when(virtualMachineProfile).getServiceOffering(); + Mockito.doReturn(vmTemplateVO).when(virtualMachineProfile).getTemplate(); + Mockito.doReturn(hostTag).when(serviceOffering).getHostTag(); + Mockito.doReturn(hosts).when(randomAllocator).retrieveHosts(Mockito.any(Host.Type.class), Mockito.nullable(List.class), Mockito.any(VMTemplateVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hosts).when(randomAllocator).filterAvailableHosts(Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any(ServiceOffering.class)); + List suitableHosts = randomAllocator.findSuitableHosts(virtualMachineProfile, deploymentPlan, type, excludeList, null, HostAllocator.RETURN_UPTO_ALL, considerReservedCapacity); + + Mockito.verify(randomAllocator, Mockito.times(1)).filterAvailableHosts(Mockito.any(DeploymentPlanner.ExcludeList.class), Mockito.anyInt(), Mockito.anyBoolean(), Mockito.anyList(), Mockito.any(ServiceOffering.class)); + Assert.assertEquals(2, suitableHosts.size()); + } + + @Test + public void filterAvailableHostsTestAvailableHostsReachedReturnUpToLimitShouldReturnOnlyHostsWithinLimit() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = 1; + + Mockito.doReturn(false).when(excludeList).shouldAvoid(Mockito.any(Host.class)); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(Mockito.anyBoolean(), Mockito.any(ServiceOffering.class), Mockito.any(Host.class)); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(1, suitableHosts.size()); + } + + @Test + public void filterAvailableHostsTestReturnUpToAllShouldReturnAllAvailableHosts() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = HostAllocator.RETURN_UPTO_ALL; + + Mockito.doReturn(false).when(excludeList).shouldAvoid(Mockito.any(Host.class)); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(Mockito.anyBoolean(), Mockito.any(ServiceOffering.class), Mockito.any(Host.class)); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(2, suitableHosts.size()); + } + + @Test + public void filterAvailableHostsTestHost1InAvoidShouldOnlyReturnHost2() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = HostAllocator.RETURN_UPTO_ALL; + + Mockito.doReturn(true).when(excludeList).shouldAvoid(host1); + Mockito.doReturn(false).when(excludeList).shouldAvoid(host2); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(Mockito.anyBoolean(), Mockito.any(ServiceOffering.class), Mockito.any(Host.class)); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(1, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + } + + @Test + public void filterAvailableHostsTestOnlyHost2HasCpuCapacityAndCapabilityShouldReturnOnlyHost2() { + List hosts = new ArrayList<>(Arrays.asList(host1, host2)); + int returnUpTo = HostAllocator.RETURN_UPTO_ALL; + + Mockito.doReturn(false).when(excludeList).shouldAvoid(Mockito.any(Host.class)); + Mockito.doReturn(false).when(randomAllocator).hostHasCpuCapabilityAndCapacity(considerReservedCapacity, serviceOffering, host1); + Mockito.doReturn(true).when(randomAllocator).hostHasCpuCapabilityAndCapacity(considerReservedCapacity, serviceOffering, host2); + List suitableHosts = randomAllocator.filterAvailableHosts(excludeList, returnUpTo, considerReservedCapacity, hosts, serviceOffering); + + Assert.assertEquals(1, suitableHosts.size()); + Assert.assertEquals(host2, suitableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullAndNoHostTagAndNoTagRuleShouldOnlyReturnHostsWithNoTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullAndOnlyHostTagsRulesShouldReturnHostsThatMatchRuleTagsAndHostsWithNoTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host2)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host2, availableHosts.get(1)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullProvidedHostTagsNotNullAndNoHostWithMatchingRuleTagsShouldReturnHostWithMatchingTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullProvidedHostTagsNotNullAndHostWithMatchingRuleTagsShouldReturnHostWithHostMatchingTagsAndRuleTags() { + List upAndEnabledHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host3)); + + Mockito.doReturn(upAndEnabledHosts).when(resourceManagerMock).listAllUpAndEnabledHosts(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, null, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host3, availableHosts.get(1)); + } + + @Test + public void retrieveHostsTestProvidedHostsNotNullAndNoHostTagAndNoTagRuleShouldOnlyReturnHostsWithNoTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNotNullAndOnlyHostTagsRulesShouldReturnHostsThatMatchRuleTagsAndHostsWithNoTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithNoRuleTagsAndHostTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host2)); + + Mockito.doReturn(hostsWithNoRuleTagsAndHostTags).when(hostDao).listAllHostsThatHaveNoRuleTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, null, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host2, availableHosts.get(1)); + } + + @Test + public void retrieveHostsTestProvidedHostsNotNullProvidedHostTagsNotNullAndNoHostWithMatchingRuleTagsShouldReturnHostWithMatchingTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(emptyList).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(1, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + } + + @Test + public void retrieveHostsTestProvidedHostsNullNotProvidedHostTagsNotNullAndHostWithMatchingRuleTagsShouldReturnHostWithHostMatchingTagsAndRuleTags() { + List providedHosts = new ArrayList<>(Arrays.asList(host1, host2)); + List hostsWithMatchingTags = new ArrayList<>(Arrays.asList(host1)); + List hostsMatchingRuleTags = new ArrayList<>(Arrays.asList(host3)); + + Mockito.doReturn(hostsWithMatchingTags).when(hostDao).listByHostTag(Mockito.any(Host.Type.class), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyLong(), Mockito.anyString()); + Mockito.doReturn(hostsMatchingRuleTags).when(hostDao).findHostsWithTagRuleThatMatchComputeOfferingTags(Mockito.nullable(String.class)); + List availableHosts = randomAllocator.retrieveHosts(type, providedHosts, vmTemplateVO, hostTag, clusterId, podId, zoneId); + + Assert.assertEquals(2, availableHosts.size()); + Assert.assertEquals(host1, availableHosts.get(0)); + Assert.assertEquals(host3, availableHosts.get(1)); + } +} diff --git a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java index 9274bd1ff085..626f2cda172f 100644 --- a/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java +++ b/server/src/test/java/com/cloud/ha/HighAvailabilityManagerImplTest.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.ha.dao.HAConfigDao; import org.apache.cloudstack.managed.context.ManagedContext; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -118,6 +119,8 @@ public class HighAvailabilityManagerImplTest { @Mock ConfigurationDao _configDao; @Mock + HAConfigDao _haConfigDao; + @Mock VolumeOrchestrationService volumeMgr; @Mock ConsoleProxyManager consoleProxyManager; @@ -362,7 +365,7 @@ public void investigateHostStatusSuccess() { investigators.add(investigator); highAvailabilityManager.setInvestigators(investigators); // Mock isAgentAlive to return host status as Down - Mockito.when(investigator.isAgentAlive(hostVO)).thenReturn(Status.Down); + Mockito.when(investigator.getHostAgentStatus(hostVO)).thenReturn(Status.Down); ConfigKey haEnabled = Mockito.mock(ConfigKey.class); highAvailabilityManager.VmHaEnabled = haEnabled; diff --git a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java index d94f9db0c99c..e5239a651230 100644 --- a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java +++ b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java @@ -53,6 +53,10 @@ import com.cloud.utils.Pair; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachineProfile; +import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; + +import java.math.BigDecimal; +import java.math.RoundingMode; @RunWith(MockitoJUnitRunner.class) public class KVMGuruTest { @@ -186,79 +190,45 @@ public void testSetVmQuotaPercentageOverProvision() { } @Test - public void validateGetVmMaxMemoryReturnCustomOfferingMaxMemory(){ - int maxCustomOfferingMemory = 64; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(String.valueOf(maxCustomOfferingMemory)); - - long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1l); - - Assert.assertEquals(ByteScaleUtils.mebibytesToBytes(maxCustomOfferingMemory), result); - } - - @Test - public void validateGetVmMaxMemoryReturnVmServiceOfferingMaxRAMSize(){ - int maxMemoryConfig = 64; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(null); + public void getVmMaxMemoryTestConsiderKvmMemoryDynamicScalingCapacitySettingWhenItIsGreaterThanZero() { + int maxMemoryConfigValue = 64; - ConfigKey vmServiceOfferingMaxRAMSize = Mockito.mock(ConfigKey.class); - ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE = vmServiceOfferingMaxRAMSize; + ConfigDepotImpl configDepotMock = Mockito.mock(ConfigDepotImpl.class); + ConfigKey.init(configDepotMock); + Mockito.when(configDepotMock.getConfigStringValue(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(String.valueOf(maxMemoryConfigValue)); - Mockito.when(vmServiceOfferingMaxRAMSize.value()).thenReturn(maxMemoryConfig); - long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1l); - - Assert.assertEquals(ByteScaleUtils.mebibytesToBytes(maxMemoryConfig), result); + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", 1L, 1L); + ConfigKey.init(null); + Assert.assertEquals(ByteScaleUtils.mebibytesToBytes(maxMemoryConfigValue), result); } @Test - public void validateGetVmMaxMemoryReturnMaxHostMemory(){ + public void getVmMaxMemoryTestConsiderHostMaxMemoryWhenKvmMemoryDynamicScalingCapacitySettingIsEqualToZero() { long maxHostMemory = ByteScaleUtils.mebibytesToBytes(2000); - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_MEMORY)).thenReturn(null); - - ConfigKey vmServiceOfferingMaxRAMSize = Mockito.mock(ConfigKey.class); - ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_RAM_SIZE = vmServiceOfferingMaxRAMSize; - - Mockito.when(vmServiceOfferingMaxRAMSize.value()).thenReturn(0); - - long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", maxHostMemory); + long result = guru.getVmMaxMemory(serviceOfferingVoMock, "Vm description", maxHostMemory, 1L); Assert.assertEquals(maxHostMemory, result); } @Test - public void validateGetVmMaxCpuCoresReturnCustomOfferingMaxCpuCores(){ - int maxCustomOfferingCpuCores = 16; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(String.valueOf(maxCustomOfferingCpuCores)); - - long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", 1); - - Assert.assertEquals(maxCustomOfferingCpuCores, result); - } - - @Test - public void validateGetVmMaxCpuCoresVmServiceOfferingMaxCPUCores(){ + public void getVmMaxCpuCoresTestConsiderKvmCpuDynamicScalingCapacitySettingWhenItIsGreaterThanZero() { int maxCpuCoresConfig = 16; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(null); + ConfigDepotImpl configDepotMock = Mockito.mock(ConfigDepotImpl.class); + ConfigKey.init(configDepotMock); + Mockito.when(configDepotMock.getConfigStringValue(Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(String.valueOf(maxCpuCoresConfig)); - ConfigKey vmServiceOfferingMaxCPUCores = Mockito.mock(ConfigKey.class); - ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES = vmServiceOfferingMaxCPUCores; - - Mockito.when(vmServiceOfferingMaxCPUCores.value()).thenReturn(maxCpuCoresConfig); - long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", 1); + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", 1, 1L); + ConfigKey.init(null); Assert.assertEquals(maxCpuCoresConfig, result); } @Test - public void validateGetVmMaxCpuCoresReturnMaxHostMemory(){ + public void getVmMaxCpuCoresTestConsiderHostMaxCpuWhenKvmCpuDynamicScalingCapacitySettingIsEqualToZero() { int maxHostCpuCores = 64; - Mockito.when(serviceOfferingVoMock.getDetail(ApiConstants.MAX_CPU_NUMBER)).thenReturn(null); - - ConfigKey vmServiceOfferingMaxCPUCores = Mockito.mock(ConfigKey.class); - ConfigurationManagerImpl.VM_SERVICE_OFFERING_MAX_CPU_CORES = vmServiceOfferingMaxCPUCores; - - Mockito.when(vmServiceOfferingMaxCPUCores.value()).thenReturn(0); - - long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", maxHostCpuCores); + long result = guru.getVmMaxCpuCores(serviceOfferingVoMock, "Vm description", maxHostCpuCores, 1L); Assert.assertEquals(maxHostCpuCores, result); } @@ -321,39 +291,36 @@ public void validateConfigureVmMemoryAndCpuCoresServiceOfferingIsDynamicAndVmIsD guru.serviceOfferingDao = serviceOfferingDaoMock; Mockito.doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(Mockito.anyLong(), Mockito.anyLong()); - Mockito.doReturn(true).when(guru).isVmDynamicScalable(Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.doReturn(true).when(guru).isVmDynamicScalable(Mockito.any(), Mockito.any()); guru.configureVmMemoryAndCpuCores(vmTO, host, virtualMachineMock, vmProfile); - Mockito.verify(guru).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong()); - Mockito.verify(guru).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt()); + Mockito.verify(guru).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.verify(guru).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()); } @Test public void validateConfigureVmMemoryAndCpuCoresServiceOfferingIsNotDynamicAndVmIsDynamicDoNotCallGetMethods(){ guru.serviceOfferingDao = serviceOfferingDaoMock; - Mockito.doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(Mockito.anyLong(), Mockito.anyLong()); - Mockito.doReturn(false).when(guru).isVmDynamicScalable(Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.doReturn(false).when(guru).isVmDynamicScalable(Mockito.any(), Mockito.any()); guru.configureVmMemoryAndCpuCores(vmTO, host, virtualMachineMock, vmProfile); - Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong()); - Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt()); + Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()); } @Test public void validateConfigureVmMemoryAndCpuCoresServiceOfferingIsDynamicAndVmIsNotDynamicDoNotCallGetMethods(){ guru.serviceOfferingDao = serviceOfferingDaoMock; - Mockito.doReturn(serviceOfferingVoMock).when(serviceOfferingDaoMock).findById(Mockito.anyLong(), Mockito.anyLong()); - Mockito.doReturn(true).when(serviceOfferingVoMock).isDynamic(); Mockito.doReturn(false).when(vmTO).isEnableDynamicallyScaleVm(); guru.configureVmMemoryAndCpuCores(vmTO, host, virtualMachineMock, vmProfile); - Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong()); - Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt()); + Mockito.verify(guru, Mockito.never()).getVmMaxMemory(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyLong(), Mockito.anyLong()); + Mockito.verify(guru, Mockito.never()).getVmMaxCpuCores(Mockito.any(ServiceOfferingVO.class), Mockito.anyString(), Mockito.anyInt(), Mockito.anyLong()); } @Test @@ -506,4 +473,30 @@ public void testGetNullWhenVMThereIsNoInformationOfUsedHosts() { Assert.assertNull(clusterId); } + + @Test + public void getCpuQuotaPercentageTestAssertQuotaEqualsVmSpeedDividedByHostSpeed() { + double hostSpeed = 3000; + double vmSpeed = 500; + double expectedQuota = new BigDecimal(vmSpeed / hostSpeed).setScale(2, RoundingMode.HALF_DOWN).doubleValue(); + double actualQuota = guru.getCpuQuotaPercentage(vmSpeed, hostSpeed); + Assert.assertEquals(expectedQuota, actualQuota, 0.0001); + } + + @Test + public void getCpuQuotaPercentageTestAssertQuotaEqualsOneWhenVmSpeedIsGreaterThanHostSpeed() { + double hostSpeed = 3000; + double vmSpeed = 6000; + double expectedQuota = 1; + double actualQuota = guru.getCpuQuotaPercentage(vmSpeed, hostSpeed); + Assert.assertEquals(expectedQuota, actualQuota, 0.0001); + } + + @Test + public void getCpuQuotaPercentageTestReturnNullWhenANumberFormatExceptionIsThrown() { + double hostSpeed = 0; + double vmSpeed = 6000; + Double actualQuota = guru.getCpuQuotaPercentage(vmSpeed, hostSpeed); + Assert.assertNull(actualQuota); + } } diff --git a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java index 824d4ee47019..cf3a886ce99f 100644 --- a/server/src/test/java/com/cloud/network/IpAddressManagerTest.java +++ b/server/src/test/java/com/cloud/network/IpAddressManagerTest.java @@ -356,6 +356,7 @@ public void checkIfPublicIpAddressIsNotInQuarantineAndCanBeAllocatedTestIpIsNoLo Mockito.when(ipAddressMock.getId()).thenReturn(dummyID); Mockito.when(publicIpQuarantineDaoMock.findByPublicIpAddressId(Mockito.anyLong())).thenReturn(publicIpQuarantineVOMock); Mockito.doReturn(false).when(ipAddressManager).isPublicIpAddressStillInQuarantine(Mockito.any(PublicIpQuarantineVO.class), Mockito.any(Date.class)); + Mockito.doNothing().when(ipAddressManager).removePublicIpAddressFromQuarantine(Mockito.anyLong(), Mockito.anyString()); boolean result = ipAddressManager.canPublicIpAddressBeAllocated(ipAddressMock, newOwnerMock); diff --git a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java index 51b2dad3decd..cd7d40d68951 100644 --- a/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java +++ b/server/src/test/java/com/cloud/network/NetworkServiceImplTest.java @@ -460,7 +460,7 @@ public void testCreateGuestNetwork() throws InsufficientCapacityException, Resou null, null, false, null, accountMock, null, phyNet, 1L, null, null, null, null, null, true, null, null, null, null, null, - null, null, null, null, new Pair<>(1500, privateMtu), null); + null, null, null, null, new Pair<>(1500, privateMtu), null, true); } @Test public void testValidateMtuConfigWhenMtusExceedThreshold() { @@ -1330,4 +1330,52 @@ public void addProjectNetworksConditionToSearch_includesSpecificProjectWhenProje Mockito.verify(accountJoin).addAnd("type", SearchCriteria.Op.EQ, Account.Type.PROJECT); Mockito.verify(sc).addAnd("id", SearchCriteria.Op.SC, accountJoin); } + + @Test(expected = InvalidParameterValueException.class) + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestThrowExceptionWhenParamIsSpecifiedOnTiersCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.isForVpc()).thenReturn(true); + + service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(true, networkOfferingVO); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnTiersCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.isForVpc()).thenReturn(true); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test(expected = InvalidParameterValueException.class) + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestThrowExceptionWhenParamIsSpecifiedOnNonIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Shared); + + service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(true, networkOfferingVO); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnNonIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.L2); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnTrueByDefaultOnIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + + Assert.assertTrue(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(null, networkOfferingVO)); + } + + @Test + public void getAndValidateSupportForKeepMacAddressOnPublicNicParameterTestReturnSpecifiedValueOnIsolatedNetworksCreation() { + networkOfferingVO = Mockito.mock(NetworkOfferingVO.class); + Mockito.when(networkOfferingVO.getGuestType()).thenReturn(Network.GuestType.Isolated); + + Assert.assertFalse(service.getAndValidateSupportForKeepMacAddressOnPublicNicParameter(false, networkOfferingVO)); + } } diff --git a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java index 4237ef7f6f65..e4773d01de92 100644 --- a/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java +++ b/server/src/test/java/com/cloud/network/router/NetworkHelperImplTest.java @@ -24,18 +24,31 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; -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 com.cloud.deploy.DeployDestination; +import com.cloud.exception.InsufficientAddressCapacityException; +import com.cloud.network.Ipv6Service; +import com.cloud.network.Network; +import com.cloud.network.Networks; +import com.cloud.network.addr.PublicIp; +import com.cloud.network.dao.NetworkVO; +import com.cloud.offering.NetworkOffering; +import com.cloud.utils.net.Ip; +import com.cloud.vm.NicProfile; +import com.cloud.vm.NicVO; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.network.router.deployment.RouterDeploymentDefinition; +import org.junit.Assert; 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.Spy; import org.mockito.junit.MockitoJUnitRunner; import com.cloud.agent.AgentManager; @@ -57,6 +70,9 @@ import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; +import java.util.List; +import java.util.Map; + @RunWith(MockitoJUnitRunner.class) public class NetworkHelperImplTest { @@ -69,32 +85,59 @@ public class NetworkHelperImplTest { @Mock DomainRouterDao routerDao; + @Spy @InjectMocks - protected NetworkHelperImpl nwHelper = new NetworkHelperImpl(); + protected NetworkHelperImpl networkHelperSpy = new NetworkHelperImpl(); + @Mock NetworkOrchestrationService networkOrchestrationService; + @Mock NetworkDao networkDao; + @Mock NetworkModel networkModel; + @Mock - NicDao nicDao; + private NicDao nicDaoMock; @Mock private RouterDeploymentDefinition routerDeploymentDefinition; + @Mock private VirtualRouterProvider virtualProvider; + @Mock private Account owner; + @Mock private ServiceOfferingVO routerOffering; + @Mock private VMTemplateVO template; + @Mock + private PublicIp publicIpMock; + + @Mock + private RouterDeploymentDefinition routerDeploymentDefinitionMock; + + @Mock + private Ipv6Service ipv6ServiceMock; + + @Mock + private NicVO nicVoMock; + + @Mock + private Network networkMock; + + private NicProfile nicProfile = new NicProfile(); + @Before public void setUp() { - nwHelper._networkDao = networkDao; - nwHelper._networkModel = networkModel; + networkHelperSpy._networkDao = networkDao; + networkHelperSpy._networkModel = networkModel; + when(template.getId()).thenReturn(1L); when(template.isDynamicallyScalable()).thenReturn(true); when(virtualProvider.getId()).thenReturn(1L); @@ -107,7 +150,7 @@ public void setUp() { public void testSendCommandsToRouterWrongRouterVersion() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); doReturn(false).when(nwHelperUT).checkRouterVersion(vr); @@ -122,7 +165,7 @@ public void testSendCommandsToRouterWrongRouterVersion() public void testSendCommandsToRouter() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -160,7 +203,7 @@ public void testSendCommandsToRouter() public void testSendCommandsToRouterWithTrueResult() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -198,7 +241,7 @@ public void testSendCommandsToRouterWithTrueResult() public void testSendCommandsToRouterWithNoAnswers() throws AgentUnavailableException, OperationTimedoutException, ResourceUnavailableException { // Prepare - NetworkHelperImpl nwHelperUT = spy(this.nwHelper); + NetworkHelperImpl nwHelperUT = networkHelperSpy; VirtualRouter vr = mock(VirtualRouter.class); when(vr.getHostId()).thenReturn(HOST_ID); doReturn(true).when(nwHelperUT).checkRouterVersion(vr); @@ -227,7 +270,7 @@ public void testCreateDomainRouter_New() { boolean offerHA = false; Long vpcId = 900L; when(routerDao.persist(any(DomainRouterVO.class))).thenAnswer(invocation -> invocation.getArgument(0)); - DomainRouterVO result = nwHelper.createOrUpdateDomainRouter( + DomainRouterVO result = networkHelperSpy.createOrUpdateDomainRouter( null, id, routerDeploymentDefinition, owner, userId, routerOffering, offerHA, vpcId, template); assertNotNull(result); assertEquals(id, result.getId()); @@ -261,11 +304,234 @@ public void testUpdateDomainRouter() { owner.getDomainId(), owner.getId(), userId, routerDeploymentDefinition.isRedundant(), VirtualRouter.RedundantState.UNKNOWN, offerHA, false, vpcId); existing.setDynamicallyScalable(false); - DomainRouterVO result = nwHelper.createOrUpdateDomainRouter( + DomainRouterVO result = networkHelperSpy.createOrUpdateDomainRouter( existing, id, routerDeploymentDefinition, owner, userId, routerOffering, offerHA, vpcId, template); verify(routerDao).update(existing.getId(), existing); assertEquals(template.getId(), result.getTemplateId()); assertEquals(Hypervisor.HypervisorType.KVM, result.getHypervisorType()); assertTrue(result.isDynamicallyScalable()); } + + + private NicProfile getExpectedNicProfile(boolean vxlan, String vlanTag) { + NicProfile nic = new NicProfile(); + nic.setDefaultNic(true); + nic.setIPv4Address("192.168.0.10"); + nic.setIPv4Gateway("192.168.0.1"); + nic.setIPv4Netmask("255.255.255.0"); + nic.setMacAddress("ff-ff-ff-ff-ff-ff"); + + if (vxlan) { + nic.setBroadcastType(Networks.BroadcastDomainType.Vxlan); + nic.setBroadcastUri(Networks.BroadcastDomainType.Vxlan.toUri(vlanTag)); + nic.setIsolationUri(Networks.BroadcastDomainType.Vxlan.toUri(vlanTag)); + } else { + nic.setBroadcastType(Networks.BroadcastDomainType.Vlan); + nic.setBroadcastUri(vlanTag != null ? Networks.BroadcastDomainType.Vlan.toUri(vlanTag) : null); + nic.setIsolationUri(vlanTag != null ? Networks.IsolationType.Vlan.toUri(vlanTag) : null); + } + + return nic; + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVxLanNic() { + String vlanTag = "200"; + NicProfile expected = getExpectedNicProfile(true, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vxlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVlanNicWithVlanTag() { + String vlanTag = "200"; + NicProfile expected = getExpectedNicProfile(false, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void configurePublicVrNicBasedOnSourceNatIpTestConfigureVlanNicWithNoVlanTag() { + String vlanTag = null; + NicProfile expected = getExpectedNicProfile(false, vlanTag); + + Ip ipMock = Mockito.mock(Ip.class); + NetworkVO publicNetworkMock = Mockito.mock(NetworkVO.class); + long networkId = 1L; + Mockito.when(publicIpMock.getAddress()).thenReturn(ipMock); + Mockito.when(ipMock.addr()).thenReturn(expected.getIPv4Address()); + Mockito.when(publicIpMock.getGateway()).thenReturn(expected.getIPv4Gateway()); + Mockito.when(publicIpMock.getNetmask()).thenReturn(expected.getIPv4Netmask()); + Mockito.when(publicIpMock.getMacAddress()).thenReturn(expected.getMacAddress()); + Mockito.when(publicIpMock.getNetworkId()).thenReturn(networkId); + Mockito.when(networkDao.findById(networkId)).thenReturn(publicNetworkMock); + Mockito.when(publicNetworkMock.getBroadcastDomainType()).thenReturn(Networks.BroadcastDomainType.Vlan); + Mockito.when(publicIpMock.getVlanTag()).thenReturn(vlanTag); + + networkHelperSpy.configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + Assert.assertTrue(nicProfile.isDefaultNic()); + Assert.assertEquals(expected.getIPv4Address(), nicProfile.getIPv4Address()); + Assert.assertEquals(expected.getIPv4Gateway(), nicProfile.getIPv4Gateway()); + Assert.assertEquals(expected.getIPv4Netmask(), nicProfile.getIPv4Netmask()); + Assert.assertEquals(expected.getMacAddress(), nicProfile.getMacAddress()); + Assert.assertEquals(expected.getBroadcastType(), nicProfile.getBroadcastType()); + Assert.assertEquals(expected.getBroadCastUri(), nicProfile.getBroadCastUri()); + Assert.assertEquals(expected.getIsolationUri(), nicProfile.getIsolationUri()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDoNothingWhenThereIsNoPeer() throws InsufficientAddressCapacityException { + String newMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(newMacAddress); + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(null); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Assert.assertEquals(newMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestKeepMacAddress() throws InsufficientAddressCapacityException { + String peerMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress("ff-ff-ff-ff-ff-f1"); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn(peerMacAddress); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(true); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Assert.assertEquals(peerMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDifferentMacAddressFetchingNewSourceNatIp() throws InsufficientAddressCapacityException { + String macAddress = "ff-ff-ff-ff-ff-f1"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(macAddress); + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn(macAddress); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(false); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Mockito.verify(routerDeploymentDefinitionMock).findSourceNatIP(); + Mockito.verify(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(nicProfile, publicIpMock); + } + + @Test + public void setPublicNicMacAddressSameAsPeerNicTestDifferentMacAddressNotFetchingNewSourceNatIp() throws InsufficientAddressCapacityException { + String newMacAddress = "ff-ff-ff-ff-ff-ff"; + nicProfile.setIPv4Address("10.0.0.1"); + nicProfile.setMacAddress(newMacAddress); + + Mockito.when(nicDaoMock.findByIp4AddressAndNetworkId(Mockito.anyString(), Mockito.anyLong())).thenReturn(nicVoMock); + Mockito.when(nicVoMock.getMacAddress()).thenReturn("ff-ff-ff-ff-ff-f1"); + Mockito.when(routerDeploymentDefinitionMock.getKeepMacAddressOnPublicNic()).thenReturn(false); + + networkHelperSpy.setPublicNicMacAddressSameAsPeerNic(nicProfile, networkMock, routerDeploymentDefinitionMock); + + Mockito.verify(routerDeploymentDefinitionMock, Mockito.never()).findSourceNatIP(); + Mockito.verify(networkHelperSpy, Mockito.never()).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Assert.assertEquals(newMacAddress, nicProfile.getMacAddress()); + } + + @Test + public void configurePublicNicTestReturnEmptyMapWhenNetworkIsNotPublic() throws InsufficientAddressCapacityException { + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(false); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, false); + Assert.assertTrue(nic.isEmpty()); + } + + @Test + public void configurePublicNicTestConfigureDeviceId() throws InsufficientAddressCapacityException { + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + NetworkOffering networkOfferingMock = Mockito.mock(NetworkOffering.class); + + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(true); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Mockito.doReturn(List.of(networkOfferingMock)).when(networkModel).getSystemAccountNetworkOfferings(Mockito.any()); + Mockito.doReturn(List.of(networkMock)).when(networkOrchestrationService).setupNetwork( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean() + ); + Mockito.doNothing().when(networkHelperSpy).setPublicNicMacAddressSameAsPeerNic(Mockito.any(), Mockito.any(), Mockito.any()); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, true); + Integer nicDeviceId = nic.get(networkMock).get(0).getDeviceId(); + Assert.assertEquals(2, nicDeviceId.intValue()); + } + + @Test + public void configurePublicNicTestUpdateGuestNetworksIpv6Nic() throws InsufficientAddressCapacityException { + PublicIp publicIpMock = Mockito.mock(PublicIp.class); + NetworkOffering networkOfferingMock = Mockito.mock(NetworkOffering.class); + DeployDestination deployDestinationMock = Mockito.mock(DeployDestination.class); + + Mockito.when(routerDeploymentDefinitionMock.isPublicNetwork()).thenReturn(true); + Mockito.when(routerDeploymentDefinitionMock.getSourceNatIP()).thenReturn(publicIpMock); + Mockito.doNothing().when(networkHelperSpy).configurePublicVrNicBasedOnSourceNatIp(Mockito.any(), Mockito.any()); + Mockito.doReturn(List.of(networkOfferingMock)).when(networkModel).getSystemAccountNetworkOfferings(Mockito.any()); + Mockito.doReturn(List.of(networkMock)).when(networkOrchestrationService).setupNetwork( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyBoolean() + ); + Mockito.doNothing().when(networkHelperSpy).setPublicNicMacAddressSameAsPeerNic(Mockito.any(), Mockito.any(), Mockito.any()); + Mockito.when(routerDeploymentDefinitionMock.getGuestNetwork()).thenReturn(networkMock); + Mockito.when(routerDeploymentDefinitionMock.getDest()).thenReturn(deployDestinationMock); + + Map> nic = networkHelperSpy.configurePublicNic(routerDeploymentDefinitionMock, false); + Mockito.verify(ipv6ServiceMock).updateNicIpv6(Mockito.eq(nic.get(networkMock).get(0)), Mockito.any(), Mockito.eq(networkMock)); + } } diff --git a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java index 9569769b016d..8b8259421cd0 100644 --- a/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java +++ b/server/src/test/java/com/cloud/network/vpc/VpcManagerImplTest.java @@ -492,7 +492,7 @@ public void testCreateVpcDnsOfferingServiceFailure() { mockVpcDnsResources(false, false); try { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], null, null, null, true, 1500, null, null, null, false); + ip4Dns[0], null, null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -503,7 +503,7 @@ public void testCreateVpcDnsIpv6OfferingFailure() { mockVpcDnsResources(true, false); try { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], ip6Dns[0], null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -517,7 +517,7 @@ public void testCreateVpc() { Mockito.when(vpc.getUuid()).thenReturn("uuid"); try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -533,7 +533,7 @@ public void testCreateRoutedVpc() { doNothing().when(routedIpv4Manager).getOrCreateIpv4SubnetForVpc(any(), anyString()); try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, ip4Cidr, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, null, null, null, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } @@ -556,7 +556,7 @@ public void testCreateRoutedVpcWithDynamicRouting() { try (MockedConstruction mockCheckedReservation = Mockito.mockConstruction(CheckedReservation.class)) { manager.createVpc(zoneId, vpcOfferingId, vpcOwnerId, vpcName, vpcName, null, vpcDomain, - ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false); + ip4Dns[0], ip4Dns[1], null, null, true, 1500, 24, null, bgpPeerIds, false, true); } catch (ResourceAllocationException e) { Assert.fail(String.format("failure with exception: %s", e.getMessage())); } diff --git a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java index b569368f2482..924bf1135a67 100644 --- a/server/src/test/java/com/cloud/server/ManagementServerImplTest.java +++ b/server/src/test/java/com/cloud/server/ManagementServerImplTest.java @@ -23,6 +23,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.cloud.deploy.DataCenterDeployment; +import com.cloud.deploy.DeploymentPlanner; +import com.cloud.deploy.DeploymentPlanningManager; +import com.cloud.vm.VirtualMachineProfile; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -181,6 +185,24 @@ public class ManagementServerImplTest { @Mock HostAllocator hostAllocator; + @Mock + VirtualMachine virtualMachineMock; + + @Mock + VirtualMachineProfile virtualMachineProfileMock; + + @Mock + DataCenterDeployment dataCenterDeploymentMock; + + @Mock + DeploymentPlanner.ExcludeList excludeListMock; + + @Mock + Host hostMock; + + @Mock + DeploymentPlanningManager deploymentPlanningManagerMock; + private AutoCloseable closeable; private MockedStatic apiDBUtilsMock; @@ -1053,10 +1075,19 @@ public void testListGuestOSCategoriesByCriteria_FilterById() { @Test public void testGetExternalVmConsole() { - VirtualMachine virtualMachine = Mockito.mock(VirtualMachine.class); Host host = Mockito.mock(Host.class); - Mockito.when(extensionManager.getInstanceConsole(virtualMachine, host)).thenReturn(Mockito.mock(com.cloud.agent.api.Answer.class)); - Assert.assertNotNull(spy.getExternalVmConsole(virtualMachine, host)); - Mockito.verify(extensionManager).getInstanceConsole(virtualMachine, host); + Mockito.when(extensionManager.getInstanceConsole(virtualMachineMock, host)).thenReturn(Mockito.mock(com.cloud.agent.api.Answer.class)); + Assert.assertNotNull(spy.getExternalVmConsole(virtualMachineMock, host)); + Mockito.verify(extensionManager).getInstanceConsole(virtualMachineMock, host); + } + + @Test + public void getCapableSuitableHostsTestHostArchIsNotFilteredWhenNoSuitableHostsAreFound() { + List compatibleHosts = List.of(hostMock); + Mockito.doReturn(null).when(hostAllocator).allocateTo(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyList(), Mockito.anyInt(), Mockito.anyBoolean()); + + spy.getCapableSuitableHosts(virtualMachineMock, virtualMachineProfileMock, dataCenterDeploymentMock, compatibleHosts, excludeListMock, hostMock); + + apiDBUtilsMock.verify(() -> ApiDBUtils.listZoneClustersArchs(Mockito.anyLong()), Mockito.never()); } } diff --git a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java index ff67d73c0f43..93a06ef2097f 100644 --- a/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java +++ b/server/src/test/java/com/cloud/vm/UserVmManagerImplTest.java @@ -61,12 +61,6 @@ import java.util.TimeZone; import java.util.UUID; -import com.cloud.network.as.AutoScaleManager; -import com.cloud.network.dao.FirewallRulesDao; -import com.cloud.network.dao.IPAddressDao; -import com.cloud.network.dao.IPAddressVO; -import com.cloud.network.dao.LoadBalancerVMMapDao; -import com.cloud.network.dao.LoadBalancerVMMapVO; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.SecurityChecker; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -104,6 +98,7 @@ import org.apache.cloudstack.userdata.UserDataManager; import org.apache.cloudstack.vm.UnmanagedVMsManager; import org.apache.cloudstack.vm.lease.VMLeaseManager; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -146,6 +141,12 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.network.Network; import com.cloud.network.NetworkModel; +import com.cloud.network.as.AutoScaleManager; +import com.cloud.network.dao.FirewallRulesDao; +import com.cloud.network.dao.IPAddressDao; +import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.LoadBalancerVMMapDao; +import com.cloud.network.dao.LoadBalancerVMMapVO; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.dao.PhysicalNetworkDao; @@ -466,7 +467,7 @@ public class UserVmManagerImplTest { @Mock ServiceOfferingDetailsDao serviceOfferingDetailsDao; - private static final long vmId = 1l; + private static final long vmId = 1L; private static final long zoneId = 2L; private static final long accountId = 3L; private static final long nicId = 4L; @@ -483,8 +484,8 @@ public class UserVmManagerImplTest { String[] detailsConstants = {VmDetailConstants.MEMORY, VmDetailConstants.CPU_NUMBER, VmDetailConstants.CPU_SPEED}; - private DiskOfferingVO smallerDisdkOffering = prepareDiskOffering(5l * GiB_TO_BYTES, 1l, 1L, 2L); - private DiskOfferingVO largerDisdkOffering = prepareDiskOffering(10l * GiB_TO_BYTES, 2l, 10L, 20L); + private DiskOfferingVO smallerDisdkOffering = prepareDiskOffering(5L * GiB_TO_BYTES, 1L, 1L, 2L); + private DiskOfferingVO largerDisdkOffering = prepareDiskOffering(10L * GiB_TO_BYTES, 2L, 10L, 20L); Class expectedInvalidParameterValueException = InvalidParameterValueException.class; Class expectedCloudRuntimeException = CloudRuntimeException.class; @@ -548,15 +549,15 @@ public void validateGuestOsIdForUpdateVirtualMachineCommandTestOsTypeNull() { @Test(expected = InvalidParameterValueException.class) public void validateGuestOsIdForUpdateVirtualMachineCommandTestOsTypeNotFound() { - Mockito.when(updateVmCommand.getOsTypeId()).thenReturn(1l); + Mockito.when(updateVmCommand.getOsTypeId()).thenReturn(1L); userVmManagerImpl.validateGuestOsIdForUpdateVirtualMachineCommand(updateVmCommand); } @Test public void validateGuestOsIdForUpdateVirtualMachineCommandTestOsTypeFound() { - Mockito.when(updateVmCommand.getOsTypeId()).thenReturn(1l); - Mockito.when(guestOSDao.findById(1l)).thenReturn(Mockito.mock(GuestOSVO.class)); + Mockito.when(updateVmCommand.getOsTypeId()).thenReturn(1L); + Mockito.when(guestOSDao.findById(1L)).thenReturn(Mockito.mock(GuestOSVO.class)); userVmManagerImpl.validateGuestOsIdForUpdateVirtualMachineCommand(updateVmCommand); } @@ -575,11 +576,10 @@ private ServiceOfferingVO getSvcoffering(int ramSize) { int speed = 128; boolean ha = false; - boolean useLocalStorage = false; ServiceOfferingVO serviceOffering = new ServiceOfferingVO(name, cpu, ramSize, speed, null, null, ha, displayText, false, null, false); - serviceOffering.setDiskOfferingId(1l); + serviceOffering.setDiskOfferingId(1L); return serviceOffering; } @@ -720,7 +720,6 @@ private void verifyMethodsThatAreAlwaysExecuted() throws ResourceUnavailableExce } - @SuppressWarnings("unchecked") private void configureDoNothingForMethodsThatWeDoNotWantToTest() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { Mockito.doNothing().when(userVmManagerImpl).validateInputsAndPermissionForUpdateVirtualMachineCommand(updateVmCommand); Mockito.doReturn(new ArrayList()).when(userVmManagerImpl).getSecurityGroupIdList(updateVmCommand); @@ -792,7 +791,7 @@ private void configureValidateOrReplaceMacAddressTest(int times, String macAddre } @Test - public void testValidatekeyValuePair() throws Exception { + public void testValidatekeyValuePair() { assertTrue(userVmManagerImpl.isValidKeyValuePair("is-a-template=true\nHVM-boot-policy=\nPV-bootloader=pygrub\nPV-args=hvc0")); assertTrue(userVmManagerImpl.isValidKeyValuePair("is-a-template=true HVM-boot-policy= PV-bootloader=pygrub PV-args=hvc0")); assertTrue(userVmManagerImpl.isValidKeyValuePair("nvp.vm-uuid=34b3d5ea-1c25-4bb0-9250-8dc3388bfa9b")); @@ -808,8 +807,8 @@ public void configureCustomRootDiskSizeTest() { String vmDetailsRootDiskSize = "123"; Map customParameters = new HashMap<>(); customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, vmDetailsRootDiskSize); - long expectedRootDiskSize = 123l * GiB_TO_BYTES; - long offeringRootDiskSize = 0l; + long expectedRootDiskSize = 123L * GiB_TO_BYTES; + long offeringRootDiskSize = 0L; prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); } @@ -818,8 +817,8 @@ public void configureCustomRootDiskSizeTestExpectExceptionZero() { String vmDetailsRootDiskSize = "0"; Map customParameters = new HashMap<>(); customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, vmDetailsRootDiskSize); - long expectedRootDiskSize = 0l; - long offeringRootDiskSize = 0l; + long expectedRootDiskSize = 0L; + long offeringRootDiskSize = 0L; prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); } @@ -828,31 +827,31 @@ public void configureCustomRootDiskSizeTestExpectExceptionNegativeNum() { String vmDetailsRootDiskSize = "-123"; Map customParameters = new HashMap<>(); customParameters.put(VmDetailConstants.ROOT_DISK_SIZE, vmDetailsRootDiskSize); - long expectedRootDiskSize = -123l * GiB_TO_BYTES; - long offeringRootDiskSize = 0l; + long expectedRootDiskSize = -123L * GiB_TO_BYTES; + long offeringRootDiskSize = 0L; prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); } @Test public void configureCustomRootDiskSizeTestEmptyParameters() { Map customParameters = new HashMap<>(); - long expectedRootDiskSize = 99l * GiB_TO_BYTES; - long offeringRootDiskSize = 0l; + long expectedRootDiskSize = 99L * GiB_TO_BYTES; + long offeringRootDiskSize = 0L; prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); } @Test public void configureCustomRootDiskSizeTestEmptyParametersAndOfferingRootSize() { Map customParameters = new HashMap<>(); - long expectedRootDiskSize = 10l * GiB_TO_BYTES; - long offeringRootDiskSize = 10l * GiB_TO_BYTES; + long expectedRootDiskSize = 10L * GiB_TO_BYTES; + long offeringRootDiskSize = 10L * GiB_TO_BYTES; prepareAndRunConfigureCustomRootDiskSizeTest(customParameters, expectedRootDiskSize, 1, offeringRootDiskSize); } private void prepareAndRunConfigureCustomRootDiskSizeTest(Map customParameters, long expectedRootDiskSize, int timesVerifyIfHypervisorSupports, Long offeringRootDiskSize) { VMTemplateVO template = Mockito.mock(VMTemplateVO.class); - Mockito.when(template.getId()).thenReturn(1l); + Mockito.when(template.getId()).thenReturn(1L); Mockito.when(template.getSize()).thenReturn(99L * GiB_TO_BYTES); Mockito.when(templateDao.findById(Mockito.anyLong())).thenReturn(template); @@ -921,7 +920,7 @@ public void prepareResizeVolumeCmdTestSameOfferingSize() { @Test public void prepareResizeVolumeCmdTestOfferingRootSizeZero() { - DiskOfferingVO rootSizeZero = prepareDiskOffering(0l, 3l, 100L, 200L); + DiskOfferingVO rootSizeZero = prepareDiskOffering(0L, 3L, 100L, 200L); prepareAndRunResizeVolumeTest(null, 100L, 200L, smallerDisdkOffering, rootSizeZero); } @@ -931,7 +930,7 @@ public void prepareResizeVolumeCmdTestNewOfferingSmaller() { } private void prepareAndRunResizeVolumeTest(Long expectedOfferingId, long expectedMinIops, long expectedMaxIops, DiskOfferingVO currentRootDiskOffering, DiskOfferingVO newRootDiskOffering) { - long rootVolumeId = 1l; + long rootVolumeId = 1L; VolumeVO rootVolumeOfVm = Mockito.mock(VolumeVO.class); Mockito.when(rootVolumeOfVm.getId()).thenReturn(rootVolumeId); @@ -978,7 +977,7 @@ public void testUserDataAllowOverride() { String finalUserdata = userVmManagerImpl.finalizeUserData(null, userDataId, template); - Assert.assertEquals(finalUserdata, templateUserData); + Assert.assertEquals(templateUserData, finalUserdata); } @Test @@ -995,7 +994,7 @@ public void testUserDataWithoutTemplate() { String finalUserdata = userVmManagerImpl.finalizeUserData(null, userDataId, template); - Assert.assertEquals(finalUserdata, userData); + Assert.assertEquals(userData, finalUserdata); } @Test @@ -1011,7 +1010,7 @@ public void testUserDataAllowOverrideWithoutAPIuserdata() { String finalUserdata = userVmManagerImpl.finalizeUserData(null, null, template); - Assert.assertEquals(finalUserdata, templateUserData); + Assert.assertEquals(templateUserData, finalUserdata); } @Test @@ -1022,7 +1021,7 @@ public void testUserDataAllowOverrideWithUserdataText() { String finalUserdata = userVmManagerImpl.finalizeUserData(userData, null, template); - Assert.assertEquals(finalUserdata, userData); + Assert.assertEquals(userData, finalUserdata); } @Test(expected = InvalidParameterValueException.class) @@ -1043,9 +1042,7 @@ public void testResetVMUserDataVMStateNotStopped() { try { userVmManagerImpl.resetVMUserData(cmd); - } catch (ResourceUnavailableException e) { - throw new RuntimeException(e); - } catch (InsufficientCapacityException e) { + } catch (ResourceUnavailableException | InsufficientCapacityException e) { throw new RuntimeException(e); } } @@ -1071,9 +1068,7 @@ public void testResetVMUserDataDontAcceptBothUserdataAndUserdataId() { try { userVmManagerImpl.resetVMUserData(cmd); - } catch (ResourceUnavailableException e) { - throw new RuntimeException(e); - } catch (InsufficientCapacityException e) { + } catch (ResourceUnavailableException | InsufficientCapacityException e) { throw new RuntimeException(e); } } @@ -1107,14 +1102,12 @@ public void testResetVMUserDataSuccessResetWithUserdata() { try { doNothing().when(userVmManagerImpl).updateUserData(userVmVO); userVmManagerImpl.resetVMUserData(cmd); - } catch (ResourceUnavailableException e) { - throw new RuntimeException(e); - } catch (InsufficientCapacityException e) { + } catch (ResourceUnavailableException | InsufficientCapacityException e) { throw new RuntimeException(e); } Assert.assertEquals("testUserdata", userVmVO.getUserData()); - Assert.assertEquals(null, userVmVO.getUserDataId()); + Assert.assertNull(userVmVO.getUserDataId()); } @Test @@ -1148,9 +1141,7 @@ public void testResetVMUserDataSuccessResetWithUserdataId() { try { doNothing().when(userVmManagerImpl).updateUserData(userVmVO); userVmManagerImpl.resetVMUserData(cmd); - } catch (ResourceUnavailableException e) { - throw new RuntimeException(e); - } catch (InsufficientCapacityException e) { + } catch (ResourceUnavailableException | InsufficientCapacityException e) { throw new RuntimeException(e); } @@ -1523,10 +1514,10 @@ public void testRestoreVMWithVolumeSnapshots() throws ResourceUnavailableExcepti } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - long userId = 1l; - long accountId = 2l; - long newTemplateId = 2l; + public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableException, InsufficientCapacityException { + long userId = 1L; + long accountId = 2L; + long newTemplateId = 2L; when(accountMock.getId()).thenReturn(userId); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); when(userVmVoMock.getAccountId()).thenReturn(accountId); @@ -1536,10 +1527,10 @@ public void testRestoreVirtualMachineNoOwner() throws ResourceUnavailableExcepti } @Test(expected = PermissionDeniedException.class) - public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - long userId = 1l; - long accountId = 2l; - long newTemplateId = 2l; + public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableException, InsufficientCapacityException { + long userId = 1L; + long accountId = 2L; + long newTemplateId = 2L; when(accountMock.getId()).thenReturn(userId); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); when(userVmVoMock.getAccountId()).thenReturn(accountId); @@ -1550,10 +1541,10 @@ public void testRestoreVirtualMachineOwnerDisabled() throws ResourceUnavailableE } @Test(expected = CloudRuntimeException.class) - public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - long userId = 1l; - long accountId = 2l; - long newTemplateId = 2l; + public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailableException, InsufficientCapacityException { + long userId = 1L; + long accountId = 2L; + long newTemplateId = 2L; when(accountMock.getId()).thenReturn(userId); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); when(userVmVoMock.getAccountId()).thenReturn(accountId); @@ -1565,11 +1556,11 @@ public void testRestoreVirtualMachineNotInRightState() throws ResourceUnavailabl } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - long userId = 1l; - long accountId = 2l; - long currentTemplateId = 1l; - long newTemplateId = 2l; + public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableException, InsufficientCapacityException { + long userId = 1L; + long accountId = 2L; + long currentTemplateId = 1L; + long newTemplateId = 2L; when(accountMock.getId()).thenReturn(userId); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); when(userVmVoMock.getAccountId()).thenReturn(accountId); @@ -1580,17 +1571,17 @@ public void testRestoreVirtualMachineNoRootVolume() throws ResourceUnavailableEx VMTemplateVO currentTemplate = Mockito.mock(VMTemplateVO.class); when(templateDao.findById(currentTemplateId)).thenReturn(currentTemplate); - when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(new ArrayList()); + when(volumeDaoMock.findByInstanceAndType(vmId, Volume.Type.ROOT)).thenReturn(new ArrayList<>()); userVmManagerImpl.restoreVirtualMachine(accountMock, vmId, newTemplateId, null, false, null); } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - long userId = 1l; - long accountId = 2l; - long currentTemplateId = 1l; - long newTemplateId = 2l; + public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnavailableException, InsufficientCapacityException { + long userId = 1L; + long accountId = 2L; + long currentTemplateId = 1L; + long newTemplateId = 2L; when(accountMock.getId()).thenReturn(userId); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); when(userVmVoMock.getAccountId()).thenReturn(accountId); @@ -1613,11 +1604,11 @@ public void testRestoreVirtualMachineMoreThanOneRootVolume() throws ResourceUnav } @Test(expected = InvalidParameterValueException.class) - public void testRestoreVirtualMachineWithVMSnapshots() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - long userId = 1l; - long accountId = 2l; - long currentTemplateId = 1l; - long newTemplateId = 2l; + public void testRestoreVirtualMachineWithVMSnapshots() throws ResourceUnavailableException, InsufficientCapacityException { + long userId = 1L; + long accountId = 2L; + long currentTemplateId = 1L; + long newTemplateId = 2L; when(accountMock.getId()).thenReturn(userId); when(userVmDao.findById(vmId)).thenReturn(userVmVoMock); when(userVmVoMock.getAccountId()).thenReturn(accountId); @@ -1647,9 +1638,9 @@ public void addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecifiedTe userVmManagerImpl.addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecified(null, customParameters, detailsConstant, currentValue); } - Assert.assertEquals(customParameters.get(VmDetailConstants.MEMORY), "2048"); - Assert.assertEquals(customParameters.get(VmDetailConstants.CPU_NUMBER), "4"); - Assert.assertEquals(customParameters.get(VmDetailConstants.CPU_SPEED), "1000"); + Assert.assertEquals("2048", customParameters.get(VmDetailConstants.MEMORY)); + Assert.assertEquals("4", customParameters.get(VmDetailConstants.CPU_NUMBER)); + Assert.assertEquals("1000", customParameters.get(VmDetailConstants.CPU_SPEED)); } @Test @@ -1688,9 +1679,9 @@ public void addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecifiedTe userVmManagerImpl.addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecified(321, customParameters, detailsConstant, currentValue); } - Assert.assertEquals(customParameters.get(VmDetailConstants.MEMORY), "2048"); - Assert.assertEquals(customParameters.get(VmDetailConstants.CPU_NUMBER), "4"); - Assert.assertEquals(customParameters.get(VmDetailConstants.CPU_SPEED),"1000"); + Assert.assertEquals("2048", customParameters.get(VmDetailConstants.MEMORY)); + Assert.assertEquals("4", customParameters.get(VmDetailConstants.CPU_NUMBER)); + Assert.assertEquals("1000", customParameters.get(VmDetailConstants.CPU_SPEED)); } @Test @@ -1699,7 +1690,7 @@ public void updateInstanceDetailsMapWithCurrentValuesForAbsentDetailsTestAllCons Mockito.doReturn(1L).when(vmInstanceMock).getId(); Mockito.doReturn(1L).when(vmInstanceMock).getServiceOfferingId(); Mockito.doReturn(serviceOffering).when(_serviceOfferingDao).findByIdIncludingRemoved(Mockito.anyLong(), Mockito.anyLong()); - userVmManagerImpl.updateInstanceDetailsMapWithCurrentValuesForAbsentDetails(null, vmInstanceMock, 0l); + userVmManagerImpl.updateInstanceDetailsMapWithCurrentValuesForAbsentDetails(null, vmInstanceMock, 0L); Mockito.verify(userVmManagerImpl).addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecified(Mockito.any(), Mockito.any(), Mockito.eq(VmDetailConstants.CPU_SPEED), Mockito.any()); Mockito.verify(userVmManagerImpl).addCurrentDetailValueToInstanceDetailsMapIfNewValueWasNotSpecified(Mockito.any(), Mockito.any(), Mockito.eq(VmDetailConstants.MEMORY), Mockito.any()); @@ -1862,10 +1853,10 @@ public void checkExpungeVmPermissionTestAccountIsAdminHasApiAccessReturnNothing @Test public void validateIfVmSupportsMigrationTestVmIsNullThrowsInvalidParameterValueException() { - String expectedMessage = String.format("There is no VM by ID [%s].", 1l); + String expectedMessage = String.format("There is no VM by ID [%s].", 1L); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmSupportsMigration(null, 1l); + userVmManagerImpl.validateIfVmSupportsMigration(null, 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -1877,7 +1868,7 @@ public void validateIfVmSupportsMigrationTestVmIsRunningThrowsInvalidParameterVa Mockito.doReturn(VirtualMachine.State.Running).when(userVmVoMock).getState(); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmSupportsMigration(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmSupportsMigration(userVmVoMock, 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -1888,7 +1879,7 @@ public void validateIfVmSupportsMigrationTestVmIsSharedFileSystemInstanceThrowsI Mockito.doReturn(UserVmManager.SHAREDFSVM).when(userVmVoMock).getUserVmType(); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmSupportsMigration(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmSupportsMigration(userVmVoMock, 1L); }); Assert.assertEquals("Migration is not supported for Shared FileSystem Instances.", assertThrows.getMessage()); @@ -1896,13 +1887,13 @@ public void validateIfVmSupportsMigrationTestVmIsSharedFileSystemInstanceThrowsI @Test public void validateIfVmSupportsMigrationTestVmIsNotRunningDoesNotThrowInvalidParameterValueException() { - userVmManagerImpl.validateIfVmSupportsMigration(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmSupportsMigration(userVmVoMock, 1L); } @Test public void validateOldAndNewAccountsTestBothAreValidDoNothing() { Account newAccount = Mockito.mock(Account.class); - Mockito.doReturn(1l).when(newAccount).getAccountId(); + Mockito.doReturn(1L).when(newAccount).getAccountId(); userVmManagerImpl.validateOldAndNewAccounts(accountMock, newAccount, 1L, "", 1L); } @@ -1923,7 +1914,7 @@ public void validateOldAndNewAccountsTestNewAccountIsNullThrowsInvalidParameterV String expectedMessage = String.format("Invalid new account [%s] for VM in domain [%s].", assignVmCmdMock.getAccountName(), assignVmCmdMock.getDomainId()); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateOldAndNewAccounts(accountMock, null, 1l, assignVmCmdMock.getAccountName(), assignVmCmdMock.getDomainId()); + userVmManagerImpl.validateOldAndNewAccounts(accountMock, null, 1L, assignVmCmdMock.getAccountName(), assignVmCmdMock.getDomainId()); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -1931,12 +1922,12 @@ public void validateOldAndNewAccountsTestNewAccountIsNullThrowsInvalidParameterV @Test public void validateOldAndNewAccountsTestNewAccountStateIsDisabledThrowsInvalidParameterValueException() { - String expectedMessage = String.format("The new account owner [%s] is disabled.", accountMock.toString()); + String expectedMessage = String.format("The new account owner [%s] is disabled.", accountMock); Mockito.doReturn(Account.State.DISABLED).when(accountMock).getState(); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateOldAndNewAccounts(accountMock, accountMock, 1l, "", 1l); + userVmManagerImpl.validateOldAndNewAccounts(accountMock, accountMock, 1L, "", 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -1944,12 +1935,12 @@ public void validateOldAndNewAccountsTestNewAccountStateIsDisabledThrowsInvalidP @Test public void validateOldAndNewAccountsTestOldAccountIsTheSameAsNewAccountThrowsInvalidParameterValueException() { - String expectedMessage = String.format("The new account [%s] is the same as the old account.", accountMock.toString()); + String expectedMessage = String.format("The new account [%s] is the same as the old account.", accountMock); Mockito.doReturn(Account.State.ENABLED).when(accountMock).getState(); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateOldAndNewAccounts(accountMock, accountMock, 1l, "", 1l); + userVmManagerImpl.validateOldAndNewAccounts(accountMock, accountMock, 1L, "", 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -1958,9 +1949,9 @@ public void validateOldAndNewAccountsTestOldAccountIsTheSameAsNewAccountThrowsIn @Test public void validateOldAndNewAccountsTestOldAccountIsNotTheSameAsNewAccountDoesNotThrowInvalidParameterValueException() { AccountVO oldAccount = new AccountVO(); - Mockito.doReturn(1l).when(accountMock).getAccountId(); + Mockito.doReturn(1L).when(accountMock).getAccountId(); - userVmManagerImpl.validateOldAndNewAccounts(oldAccount, accountMock, 1l, "", 1l); + userVmManagerImpl.validateOldAndNewAccounts(oldAccount, accountMock, 1L, "", 1L); } @Test @@ -1980,7 +1971,7 @@ public void validateIfVmHasNoRulesTestPortForwardingRulesExistThrowsInvalidParam Mockito.doReturn(portForwardingRulesListMock).when(portForwardingRulesDaoMock).listByVm(Mockito.anyLong()); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -1993,7 +1984,7 @@ public void validateIfVmHasNoRulesTestStaticNatRulesExistThrowsInvalidParameterV Mockito.doReturn(firewallRuleVoListMock).when(firewallRulesDaoMock).listStaticNatByVmId(Mockito.anyLong()); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -2006,7 +1997,7 @@ public void validateIfVmHasNoRulesTestLoadBalancingRulesExistThrowsInvalidParame Mockito.doReturn(loadBalancerVmMapVoListMock).when(loadBalancerVmMapDaoMock).listByInstanceId(Mockito.anyLong()); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -2016,14 +2007,14 @@ public void validateIfVmHasNoRulesTestLoadBalancingRulesExistThrowsInvalidParame public void validateIfVmHasNoRulesTestOneToOneNatRulesExistThrowsInvalidParameterValueException() { String expectedMessage = String.format("Remove the One to One Nat rule for VM [%s] for IP [%s].", userVmVoMock, ipAddressVoMock.toString()); - LinkedList ipAddressVoList = new LinkedList(); + LinkedList ipAddressVoList = new LinkedList<>(); Mockito.doReturn(ipAddressVoList).when(ipAddressDaoMock).findAllByAssociatedVmId(Mockito.anyLong()); ipAddressVoList.add(ipAddressVoMock); Mockito.doReturn(true).when(ipAddressVoMock).isOneToOneNat(); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { - userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1L); }); Assert.assertEquals(expectedMessage, assertThrows.getMessage()); @@ -2031,13 +2022,13 @@ public void validateIfVmHasNoRulesTestOneToOneNatRulesExistThrowsInvalidParamete @Test public void validateIfVmHasNoRulesTestOneToOneNatRulesDoNotExistDoesNotThrowInvalidParameterValueException() { - userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1l); + userVmManagerImpl.validateIfVmHasNoRules(userVmVoMock, 1L); } @Test public void verifyResourceLimitsForAccountAndStorageTestCountOnlyRunningVmsInResourceLimitationIsTrueDoesNotCallVmResourceLimitCheck() throws ResourceAllocationException { List reservations = new ArrayList<>(); - LinkedList volumeVoList = new LinkedList(); + LinkedList volumeVoList = new LinkedList<>(); Mockito.doReturn(true).when(userVmManagerImpl).countOnlyRunningVmsInResourceLimitation(); userVmManagerImpl.verifyResourceLimitsForAccountAndStorage(accountMock, userVmVoMock, serviceOfferingVoMock, volumeVoList, virtualMachineTemplateMock, reservations); @@ -2049,7 +2040,7 @@ public void verifyResourceLimitsForAccountAndStorageTestCountOnlyRunningVmsInRes @Test public void verifyResourceLimitsForAccountAndStorageTestCountOnlyRunningVmsInResourceLimitationIsFalseCallsVmResourceLimitCheck() throws ResourceAllocationException { List reservations = new ArrayList<>(); - LinkedList volumeVoList = new LinkedList(); + LinkedList volumeVoList = new LinkedList<>(); Mockito.doReturn(false).when(userVmManagerImpl).countOnlyRunningVmsInResourceLimitation(); userVmManagerImpl.verifyResourceLimitsForAccountAndStorage(accountMock, userVmVoMock, serviceOfferingVoMock, volumeVoList, virtualMachineTemplateMock, reservations); @@ -2078,7 +2069,7 @@ public void validateIfNewOwnerHasAccessToTemplateTestCallCheckAccessWhenTemplate @Test public void updateVmOwnerTestCallsSetAccountIdSetDomainIdAndPersist() { - userVmManagerImpl.updateVmOwner(accountMock, userVmVoMock, 1l, 1l); + userVmManagerImpl.updateVmOwner(accountMock, userVmVoMock, 1L, 1L); Mockito.verify(userVmVoMock).setAccountId(Mockito.anyLong()); Mockito.verify(userVmVoMock).setDomainId(Mockito.anyLong()); @@ -2139,7 +2130,7 @@ public void addDefaultNetworkToNetworkListTestDefaultNetworkIsNotNullAddNetworkT @Test public void allocateNetworksForVmTestCallsNetworkManagerAllocate() throws InsufficientCapacityException { - LinkedHashMap> networks = new LinkedHashMap>(); + LinkedHashMap> networks = new LinkedHashMap<>(); Mockito.doReturn(userVmVoMock).when(virtualMachineManager).findById(Mockito.anyLong()); @@ -2151,7 +2142,7 @@ public void allocateNetworksForVmTestCallsNetworkManagerAllocate() throws Insuff @Test public void addSecurityGroupsToVmTestIsVmWareAndSecurityGroupIdListIsNotNullThrowsInvalidParameterValueException() { String expectedMessage = "Security group feature is not supported for VMWare hypervisor."; - LinkedList securityGroupIdList = new LinkedList(); + LinkedList securityGroupIdList = new LinkedList<>(); Mockito.doReturn(Hypervisor.HypervisorType.VMware).when(virtualMachineTemplateMock).getHypervisorType(); @@ -2164,7 +2155,7 @@ public void addSecurityGroupsToVmTestIsVmWareAndSecurityGroupIdListIsNotNullThro @Test public void addSecurityGroupsToVmTestIsNotVmWareDefaultNetworkIsNullAndNetworkModelCanAddDefaultSecurityGroupCallsAddDefaultSecurityGroupToSecurityGroupIdList() { - LinkedList securityGroupIdList = new LinkedList(); + LinkedList securityGroupIdList = new LinkedList<>(); Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(virtualMachineTemplateMock).getHypervisorType(); Mockito.doReturn(true).when(networkModel).canAddDefaultSecurityGroup(); @@ -2178,10 +2169,10 @@ public void addSecurityGroupsToVmTestIsNotVmWareDefaultNetworkIsNullAndNetworkMo @Test public void addNetworksToNetworkIdListTestCallsKeepOldSharedNetworkForVmAndAddAdditionalNetworksToVm() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = new HashSet(); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = new HashSet<>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); userVmManagerImpl.addNetworksToNetworkIdList(userVmVoMock, accountMock, networkIdList, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2207,14 +2198,14 @@ public void getOfferingWithRequiredAvailabilityForNetworkCreationTestRequiredOff @Test public void getOfferingWithRequiredAvailabilityForNetworkCreationTestFirstOfferingIsNotEnabledThrowsInvalidParameterValueException() { - String expectedMessage = String.format("Required network offering ID [%s] is not in [%s] state.", 1l, NetworkOffering.State.Enabled); + String expectedMessage = String.format("Required network offering ID [%s] is not in [%s] state.", 1L, NetworkOffering.State.Enabled); Mockito.doReturn(networkOfferingVoListMock).when(networkOfferingDaoMock).listByAvailability(NetworkOffering.Availability.Required, false); Mockito.doReturn(networkOfferingVoMock).when(networkOfferingVoListMock).get(0); Mockito.doReturn(NetworkOffering.State.Disabled).when(networkOfferingVoMock).getState(); - Mockito.doReturn(1l).when(networkOfferingVoMock).getId(); + Mockito.doReturn(1L).when(networkOfferingVoMock).getId(); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { userVmManagerImpl.getOfferingWithRequiredAvailabilityForNetworkCreation(); @@ -2237,9 +2228,9 @@ public void selectApplicableNetworkToCreateVmTestVirtualNetworkIsEmptyThrowsExce @Test public void selectApplicableNetworkToCreateVmTestVirtualNetworkHasMultipleNetworksThrowsInvalidParameterValueException() { - String expectedMessage = String.format("More than one default isolated network has been found for account [%s]; please specify networkIDs.", accountMock.toString()); - HashSet applicableNetworks = new HashSet(); - LinkedList virtualNetworks = new LinkedList(); + String expectedMessage = String.format("More than one default isolated network has been found for account [%s]; please specify networkIDs.", accountMock); + HashSet applicableNetworks = new HashSet<>(); + LinkedList virtualNetworks = new LinkedList<>(); Mockito.doReturn(virtualNetworks).when(networkModel).listNetworksForAccount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); @@ -2255,7 +2246,7 @@ public void selectApplicableNetworkToCreateVmTestVirtualNetworkHasMultipleNetwor @Test public void selectApplicableNetworkToCreateVmTestVirtualNetworkHasOneNetworkCallsNetworkDaoFindById() throws InsufficientCapacityException, ResourceAllocationException { - HashSet applicableNetworks = new HashSet(); + HashSet applicableNetworks = new HashSet<>(); Mockito.doReturn(networkVoListMock).when(networkModel).listNetworksForAccount(Mockito.anyLong(), Mockito.anyLong(), Mockito.any()); @@ -2271,30 +2262,30 @@ public void selectApplicableNetworkToCreateVmTestVirtualNetworkHasOneNetworkCall @Test public void addDefaultSecurityGroupToSecurityGroupIdListTestDefaultGroupIsNullCallsCreateSecurityGroup() { String expected = ""; - LinkedList securityGroupIdList = Mockito.spy(new LinkedList()); + LinkedList securityGroupIdList = Mockito.spy(new LinkedList<>()); Mockito.doReturn(null).when(securityGroupManagerMock).getDefaultSecurityGroup(Mockito.anyLong()); Mockito.doReturn(securityGroupVoMock).when(securityGroupManagerMock).createSecurityGroup(SecurityGroupManager.DEFAULT_GROUP_NAME, - SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, 1l, 1l, expected); + SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, 1L, 1L, expected); - Mockito.doReturn(1l).when(accountMock).getDomainId(); - Mockito.doReturn(1l).when(accountMock).getId(); + Mockito.doReturn(1L).when(accountMock).getDomainId(); + Mockito.doReturn(1L).when(accountMock).getId(); Mockito.doReturn(expected).when(accountMock).getAccountName(); - Mockito.doReturn(1l).when(securityGroupVoMock).getId(); + Mockito.doReturn(1L).when(securityGroupVoMock).getId(); userVmManagerImpl.addDefaultSecurityGroupToSecurityGroupIdList(accountMock, securityGroupIdList); - Mockito.verify(securityGroupManagerMock).createSecurityGroup(SecurityGroupManager.DEFAULT_GROUP_NAME, SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, 1l, 1l, expected); - Mockito.verify(securityGroupIdList).add(1l); + Mockito.verify(securityGroupManagerMock).createSecurityGroup(SecurityGroupManager.DEFAULT_GROUP_NAME, SecurityGroupManager.DEFAULT_GROUP_DESCRIPTION, 1L, 1L, expected); + Mockito.verify(securityGroupIdList).add(1L); } @Test public void addDefaultSecurityGroupToSecurityGroupIdListTestDefaultGroupIsPresentDoesNotCallAddIdToSecurityGroupIdList() { - LinkedList securityGroupIdList = Mockito.spy(new LinkedList()); + LinkedList securityGroupIdList = Mockito.spy(new LinkedList<>()); - securityGroupIdList.addFirst(1l); + securityGroupIdList.addFirst(1L); Mockito.doReturn(securityGroupVoMock).when(securityGroupManagerMock).getDefaultSecurityGroup(Mockito.anyLong()); - Mockito.doReturn(1l).when(securityGroupVoMock).getId(); + Mockito.doReturn(1L).when(securityGroupVoMock).getId(); userVmManagerImpl.addDefaultSecurityGroupToSecurityGroupIdList(accountMock, securityGroupIdList); @@ -2303,24 +2294,24 @@ public void addDefaultSecurityGroupToSecurityGroupIdListTestDefaultGroupIsPresen @Test public void addDefaultSecurityGroupToSecurityGroupIdListTestDefaultGroupIsNotPresentCallsAddIdToSecurityGroupIdList() { - LinkedList securityGroupIdList = Mockito.spy(new LinkedList()); + LinkedList securityGroupIdList = Mockito.spy(new LinkedList<>()); Mockito.doReturn(securityGroupVoMock).when(securityGroupManagerMock).getDefaultSecurityGroup(Mockito.anyLong()); - Mockito.doReturn(1l).when(securityGroupVoMock).getId(); + Mockito.doReturn(1L).when(securityGroupVoMock).getId(); userVmManagerImpl.addDefaultSecurityGroupToSecurityGroupIdList(accountMock, securityGroupIdList); - Mockito.verify(securityGroupIdList).add(1l); + Mockito.verify(securityGroupIdList).add(1L); } @Test public void keepOldSharedNetworkForVmTestNetworkIdListIsNotNullOrEmptyDoesNotCallFindDefaultNicForVm() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = new HashSet(); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = new HashSet<>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); userVmManagerImpl.keepOldSharedNetworkForVm(userVmVoMock, accountMock, networkIdList, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2329,9 +2320,9 @@ public void keepOldSharedNetworkForVmTestNetworkIdListIsNotNullOrEmptyDoesNotCal @Test public void keepOldSharedNetworkForVmTestNetworkIdListIsNullCallsFindDefaultNicForVm() { - HashSet applicableNetworks = new HashSet(); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + HashSet applicableNetworks = new HashSet<>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); userVmManagerImpl.keepOldSharedNetworkForVm(userVmVoMock, accountMock, null, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2340,10 +2331,10 @@ public void keepOldSharedNetworkForVmTestNetworkIdListIsNullCallsFindDefaultNicF @Test public void keepOldSharedNetworkForVmTestNetworkIdListIsEmptyCallsFindDefaultNicForVm() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = new HashSet(); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = new HashSet<>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); userVmManagerImpl.keepOldSharedNetworkForVm(userVmVoMock, accountMock, networkIdList, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2352,9 +2343,9 @@ public void keepOldSharedNetworkForVmTestNetworkIdListIsEmptyCallsFindDefaultNic @Test public void keepOldSharedNetworkForVmTestDefaultNicOldIsNullDoesNotCallNetworkDaoFindById() { - HashSet applicableNetworks = new HashSet(); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + HashSet applicableNetworks = new HashSet<>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); Mockito.doReturn(null).when(nicDao).findDefaultNicForVM(Mockito.anyLong()); @@ -2365,9 +2356,9 @@ public void keepOldSharedNetworkForVmTestDefaultNicOldIsNullDoesNotCallNetworkDa @Test public void keepOldSharedNetworkForVmTestDefaultNicOldIsNotNullCallsNetworkDaoFindById() { - HashSet applicableNetworks = new HashSet(); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + HashSet applicableNetworks = new HashSet<>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); Mockito.doReturn(new NicVO()).when(nicDao).findDefaultNicForVM(Mockito.anyLong()); @@ -2378,9 +2369,9 @@ public void keepOldSharedNetworkForVmTestDefaultNicOldIsNotNullCallsNetworkDaoFi @Test public void keepOldSharedNetworkForVmTestAccountCanNotUseNetworkDoesNotAddNetworkToApplicableNetworks() { - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); Mockito.doReturn(new NicVO()).when(nicDao).findDefaultNicForVM(Mockito.anyLong()); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); @@ -2393,9 +2384,9 @@ public void keepOldSharedNetworkForVmTestAccountCanNotUseNetworkDoesNotAddNetwor @Test public void keepOldSharedNetworkForVmTestAccountCanUseNetworkAddsNetworkToApplicableNetworks() { - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); Mockito.doReturn(new NicVO()).when(nicDao).findDefaultNicForVM(Mockito.anyLong()); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); @@ -2408,9 +2399,9 @@ public void keepOldSharedNetworkForVmTestAccountCanUseNetworkAddsNetworkToApplic @Test public void addAdditionalNetworksToVmTestNetworkIdListIsNullDoesNotCallCheckNetworkPermissions() { - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); userVmManagerImpl.addAdditionalNetworksToVm(userVmVoMock, accountMock, null, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2419,10 +2410,10 @@ public void addAdditionalNetworksToVmTestNetworkIdListIsNullDoesNotCallCheckNetw @Test public void addAdditionalNetworksToVmTestNetworkIdListIsEmptyDoesNotCallCheckNetworkPermissions() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); userVmManagerImpl.addAdditionalNetworksToVm(userVmVoMock, accountMock, networkIdList, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2432,12 +2423,12 @@ public void addAdditionalNetworksToVmTestNetworkIdListIsEmptyDoesNotCallCheckNet @Test public void addAdditionalNetworksToVmTestNetworkIsNullThrowsInvalidParameterValueException() { String expectedMessage = "Unable to find specified Network ID."; - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { userVmManagerImpl.addAdditionalNetworksToVm(userVmVoMock, accountMock, networkIdList, applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics); @@ -2449,12 +2440,12 @@ public void addAdditionalNetworksToVmTestNetworkIsNullThrowsInvalidParameterValu @Test public void addAdditionalNetworksToVmTestNetworkOfferingIsSystemOnlyThrowsInvalidParameterValueException() { String expectedMessage = String.format("Specified network [%s] is system only and cannot be used for VM deployment.", networkMock); - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); Mockito.doReturn(networkOfferingVoMock).when(entityManager).findById(Mockito.any(), Mockito.anyLong()); @@ -2469,12 +2460,12 @@ public void addAdditionalNetworksToVmTestNetworkOfferingIsSystemOnlyThrowsInvali @Test public void addAdditionalNetworksToVmTestNetworkIsNotSharedGuestTypeDoesNotCallNicDaoFindByNtwkIdAndInstanceId() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); Mockito.doReturn(networkOfferingVoMock).when(entityManager).findById(Mockito.any(), Mockito.anyLong()); @@ -2488,12 +2479,12 @@ public void addAdditionalNetworksToVmTestNetworkIsNotSharedGuestTypeDoesNotCallN @Test public void addAdditionalNetworksToVmTestNetworkIsNotDomainAclTypeDoesNotCallNicDaoFindByNtwkIdAndInstanceId() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); Mockito.doReturn(networkOfferingVoMock).when(entityManager).findById(Mockito.any(), Mockito.anyLong()); @@ -2508,12 +2499,12 @@ public void addAdditionalNetworksToVmTestNetworkIsNotDomainAclTypeDoesNotCallNic @Test public void addAdditionalNetworksToVmTestOldNicIsNullDoesNotPutIpv4InRequestIpv4ForNics() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = Mockito.spy(new HashMap()); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = Mockito.spy(new HashMap<>()); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); Mockito.doReturn(networkOfferingVoMock).when(entityManager).findById(Mockito.any(), Mockito.anyLong()); @@ -2530,12 +2521,12 @@ public void addAdditionalNetworksToVmTestOldNicIsNullDoesNotPutIpv4InRequestIpv4 @Test public void addAdditionalNetworksToVmTestOldNicIsNotNullPutsIpv4InRequestIpv4ForNics() { - LinkedList networkIdList = new LinkedList(); - HashSet applicableNetworks = Mockito.spy(new HashSet()); - HashMap requestedIPv4ForNics = Mockito.spy(new HashMap()); - HashMap requestedIPv6ForNics = new HashMap(); + LinkedList networkIdList = new LinkedList<>(); + HashSet applicableNetworks = Mockito.spy(new HashSet<>()); + HashMap requestedIPv4ForNics = Mockito.spy(new HashMap<>()); + HashMap requestedIPv6ForNics = new HashMap<>(); - networkIdList.add(1l); + networkIdList.add(1L); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); Mockito.doReturn(networkOfferingVoMock).when(entityManager).findById(Mockito.any(), Mockito.anyLong()); @@ -2554,7 +2545,7 @@ public void addAdditionalNetworksToVmTestOldNicIsNotNullPutsIpv4InRequestIpv4For public void createApplicableNetworkToCreateVmTestPhysicalNetworkIsNullThrowsInvalidParameterValueException() { Mockito.doReturn(networkOfferingVoMock).when(userVmManagerImpl).getOfferingWithRequiredAvailabilityForNetworkCreation(); - String expectedMessage = String.format("Unable to find physical network with ID [%s] and tag [%s].", 0l, null); + String expectedMessage = String.format("Unable to find physical network with ID [%s] and tag [%s].", 0L, null); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { userVmManagerImpl.createApplicableNetworkToCreateVm(accountMock, _dcMock); }); @@ -2594,7 +2585,7 @@ public void createApplicableNetworkToCreateVmTestFirstNetworkOfferingIsNotPersis Mockito.any(), Mockito.any(), Mockito.anyBoolean(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.doReturn(networkOfferingVoMock).when(userVmManagerImpl).getOfferingWithRequiredAvailabilityForNetworkCreation(); - Mockito.doReturn(1l).when(networkMock).getId(); + Mockito.doReturn(1L).when(networkMock).getId(); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); userVmManagerImpl.createApplicableNetworkToCreateVm(accountMock, _dcMock); @@ -2670,7 +2661,7 @@ public void implementNetworkTestImplementedNetworkIsNullReturnCurrentNewNetwork( try (MockedStatic ignored = mockStatic(CallContext.class)) { Mockito.when(CallContext.current()).thenReturn(callContextMock); - Mockito.doReturn(1l).when(callContextMock).getCallingUserId(); + Mockito.doReturn(1L).when(callContextMock).getCallingUserId(); Mockito.doReturn(callerUser).when(userDao).findById(Mockito.anyLong()); Mockito.doReturn(null).when(_networkMgr).implementNetwork(Mockito.anyLong(), Mockito.any(), Mockito.any()); @@ -2689,7 +2680,7 @@ public void implementNetworkTestImplementedNetworkFirstIsNullReturnCurrentNewNet try (MockedStatic ignored = mockStatic(CallContext.class)) { Mockito.when(CallContext.current()).thenReturn(callContextMock); - Mockito.doReturn(1l).when(callContextMock).getCallingUserId(); + Mockito.doReturn(1L).when(callContextMock).getCallingUserId(); Pair implementedNetwork = Mockito.mock(Pair.class); @@ -2711,7 +2702,7 @@ public void implementNetworkTestImplementedNetworkSecondIsNullReturnCurrentNewNe try (MockedStatic ignored = mockStatic(CallContext.class)) { Mockito.when(CallContext.current()).thenReturn(callContextMock); - Mockito.doReturn(1l).when(callContextMock).getCallingUserId(); + Mockito.doReturn(1L).when(callContextMock).getCallingUserId(); Pair implementedNetwork = Mockito.mock(Pair.class); @@ -2734,7 +2725,7 @@ public void implementNetworkTestImplementedNetworkSecondIsNotNullReturnImplement try (MockedStatic ignored = mockStatic(CallContext.class)) { Mockito.when(CallContext.current()).thenReturn(callContextMock); - Mockito.doReturn(1l).when(callContextMock).getCallingUserId(); + Mockito.doReturn(1L).when(callContextMock).getCallingUserId(); Pair implementedNetwork = Mockito.mock(Pair.class); @@ -2758,7 +2749,7 @@ public void implementNetworkTestImplementedNetworkCatchException() throws Resour try (MockedStatic ignored = mockStatic(CallContext.class)) { Mockito.when(CallContext.current()).thenReturn(callContextMock); - Mockito.doReturn(1l).when(callContextMock).getCallingUserId(); + Mockito.doReturn(1L).when(callContextMock).getCallingUserId(); Pair implementedNetwork = Mockito.mock(Pair.class); @@ -2776,10 +2767,10 @@ public void implementNetworkTestImplementedNetworkCatchException() throws Resour @Test public void updateBasicTypeNetworkForVmTestNetworkIdListIsNotEmptyThrowsInvalidParameterValueException() { String expectedMessage = "Cannot move VM with Network IDs; this is a basic zone VM."; - LinkedList networkIdList = new LinkedList(); - LinkedList securityGroupIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); + LinkedList securityGroupIdList = new LinkedList<>(); - networkIdList.add(1l); + networkIdList.add(1L); InvalidParameterValueException assertThrows = Assert.assertThrows(expectedInvalidParameterValueException, () -> { userVmManagerImpl.updateBasicTypeNetworkForVm(userVmVoMock, accountMock, virtualMachineTemplateMock, virtualMachineProfileMock, _dcMock, networkIdList, @@ -2811,7 +2802,7 @@ public void updateBasicTypeNetworkForVmTestNetworkIdListIsEmptyCallsCleanupOfOld throws InsufficientCapacityException { LinkedList securityGroupIdList = Mockito.mock(LinkedList.class); - LinkedList networkIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); Mockito.doReturn(networkMock).when(networkModel).getExclusiveGuestNetwork(Mockito.anyLong()); @@ -2828,7 +2819,7 @@ public void updateBasicTypeNetworkForVmTestNetworkIdListIsEmptyCallsCleanupOfOld public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsEnabledApplicableNetworksIsEmptyThrowsInvalidParameterValueException() { String expectedMessage = "No network is specified, please specify one when you move the VM. For now, please add a network to VM on NICs tab."; LinkedList securityGroupIdList = Mockito.mock(LinkedList.class); - LinkedList networkIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); Mockito.doReturn(true).when(networkModel).checkSecurityGroupSupportForNetwork(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); @@ -2846,7 +2837,7 @@ public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsEnabledApplicableNe ResourceAllocationException { LinkedList securityGroupIdList = Mockito.mock(LinkedList.class); - LinkedList networkIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); Mockito.doReturn(new NicVO()).when(nicDao).findDefaultNicForVM(Mockito.anyLong()); Mockito.doReturn(networkMock).when(_networkDao).findById(Mockito.anyLong()); @@ -2866,9 +2857,9 @@ public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsEnabledApplicableNe public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsNotEnabledSecurityGroupIdListIsNotEmptyThrowsInvalidParameterValueException() { String expectedMessage = "Cannot move VM with security groups; security group feature is not enabled in this zone."; LinkedList securityGroupIdList = Mockito.mock(LinkedList.class); - LinkedList networkIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); - securityGroupIdList.add(1l); + securityGroupIdList.add(1L); Mockito.doReturn(false).when(networkModel).checkSecurityGroupSupportForNetwork(accountMock, _dcMock, networkIdList, securityGroupIdList); @@ -2885,7 +2876,7 @@ public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsNotEnabledApplicabl ResourceAllocationException { LinkedList securityGroupIdList = Mockito.mock(LinkedList.class); - LinkedList networkIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); Mockito.doReturn(networkMock).when(userVmManagerImpl).addNicsToApplicableNetworksAndReturnDefaultNetwork(Mockito.any(), Mockito.anyMap(), Mockito.anyMap(), Mockito.any()); Mockito.doNothing().when(userVmManagerImpl).selectApplicableNetworkToCreateVm(Mockito.any(), Mockito.any(), Mockito.any()); @@ -2908,7 +2899,7 @@ public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsNotEnabledApplicabl throws InsufficientCapacityException, ResourceAllocationException { LinkedList securityGroupIdList = Mockito.mock(LinkedList.class); - LinkedList networkIdList = new LinkedList(); + LinkedList networkIdList = new LinkedList<>(); Mockito.doReturn(false).when(networkModel).checkSecurityGroupSupportForNetwork(accountMock, _dcMock, networkIdList, securityGroupIdList); Mockito.doReturn(true).when(securityGroupIdList).isEmpty(); @@ -2929,10 +2920,10 @@ public void updateAdvancedTypeNetworkForVmTestSecurityGroupIsNotEnabledApplicabl @Test public void addNicsToApplicableNetworksAndReturnDefaultNetworkTestApplicableNetworkIsEmptyReturnNull() { - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); - LinkedHashSet applicableNetworks = new LinkedHashSet(); - LinkedHashMap> networks = new LinkedHashMap>(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); + LinkedHashSet applicableNetworks = new LinkedHashSet<>(); + LinkedHashMap> networks = new LinkedHashMap<>(); NetworkVO defaultNetwork = userVmManagerImpl.addNicsToApplicableNetworksAndReturnDefaultNetwork(applicableNetworks, requestedIPv4ForNics, requestedIPv6ForNics, networks); @@ -2941,9 +2932,9 @@ public void addNicsToApplicableNetworksAndReturnDefaultNetworkTestApplicableNetw @Test public void addNicsToApplicableNetworksAndReturnDefaultNetworkTestApplicableNetworkIsNotEmptyReturnFirstElement() { - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); - LinkedHashSet applicableNetworks = new LinkedHashSet(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); + LinkedHashSet applicableNetworks = new LinkedHashSet<>(); LinkedHashMap> networks = Mockito.spy(LinkedHashMap.class); applicableNetworks.add(networkMock); @@ -2956,9 +2947,9 @@ public void addNicsToApplicableNetworksAndReturnDefaultNetworkTestApplicableNetw @Test public void addNicsToApplicableNetworksAndReturnDefaultNetworkTestApplicableNetworkIsNotEmptyPutTwoNetworksInNetworksMapAndReturnFirst() { - HashMap requestedIPv4ForNics = new HashMap(); - HashMap requestedIPv6ForNics = new HashMap(); - LinkedHashSet applicableNetworks = new LinkedHashSet(); + HashMap requestedIPv4ForNics = new HashMap<>(); + HashMap requestedIPv6ForNics = new HashMap<>(); + LinkedHashSet applicableNetworks = new LinkedHashSet<>(); LinkedHashMap> networks = Mockito.spy(LinkedHashMap.class); NetworkVO networkVoMock2 = Mockito.mock(NetworkVO.class); @@ -2976,9 +2967,9 @@ public void validateIfVolumesHaveNoSnapshotsTestVolumeHasSnapshotsThrowsInvalidP String expectedMessage = String.format("Snapshots exist for volume [%s]. Detach volume or remove snapshots for the volume before assigning VM to another user.", volumeVOMock.getName()); - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); volumes.add(volumeVOMock); - LinkedList snapshots = new LinkedList(); + LinkedList snapshots = new LinkedList<>(); snapshots.add(snapshotVoMock); Mockito.doReturn(snapshots).when(snapshotDaoMock).listByStatusNotIn(Mockito.anyLong(), Mockito.any(), Mockito.any()); @@ -2992,9 +2983,9 @@ public void validateIfVolumesHaveNoSnapshotsTestVolumeHasSnapshotsThrowsInvalidP @Test public void validateIfVolumesHaveNoSnapshotsTestVolumeHasNoSnapshotsDoesNotThrowInvalidParameterException() { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); volumes.add(volumeVOMock); - LinkedList snapshots = new LinkedList(); + LinkedList snapshots = new LinkedList<>(); Mockito.doReturn(snapshots).when(snapshotDaoMock).listByStatusNotIn(Mockito.anyLong(), Mockito.any(), Mockito.any()); @@ -3040,7 +3031,7 @@ public void moveVmToUserTestProjectIdProvidedAndDomainIdIsNullThrowsInvalidParam Mockito.doReturn(true).when(accountManager).isRootAdmin(Mockito.anyLong()); Mockito.doReturn(userVmVoMock).when(userVmDao).findById(Mockito.anyLong()); - Mockito.doReturn(1l).when(assignVmCmdMock).getProjectId(); + Mockito.doReturn(1L).when(assignVmCmdMock).getProjectId(); Mockito.doReturn(null).when(assignVmCmdMock).getDomainId(); configureDoNothingForMethodsThatWeDoNotWantToTest(); @@ -3071,7 +3062,7 @@ public void moveVmToUserTestValidateIfVmHasNoRulesThrowsInvalidParameterValueExc public void moveVmToUserTestSnapshotsForVolumeExistThrowsInvalidParameterValueException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); volumes.add(volumeVOMock); Mockito.doReturn(true).when(accountManager).isRootAdmin(Mockito.anyLong()); @@ -3090,7 +3081,7 @@ public void moveVmToUserTestSnapshotsForVolumeExistThrowsInvalidParameterValueEx public void moveVmToUserTestVerifyResourceLimitsForAccountAndStorageThrowsResourceAllocationException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); Mockito.doReturn(true).when(accountManager).isRootAdmin(Mockito.anyLong()); Mockito.doReturn(userVmVoMock).when(userVmDao).findById(Mockito.anyLong()); @@ -3109,7 +3100,7 @@ public void moveVmToUserTestVerifyResourceLimitsForAccountAndStorageThrowsResour public void moveVmToUserTestVerifyValidateIfNewOwnerHasAccessToTemplateThrowsInvalidParameterValueException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); Mockito.doReturn(true).when(accountManager).isRootAdmin(Mockito.anyLong()); Mockito.doReturn(userVmVoMock).when(userVmDao).findById(Mockito.anyLong()); @@ -3127,7 +3118,7 @@ public void moveVmToUserTestVerifyValidateIfNewOwnerHasAccessToTemplateThrowsInv public void moveVmToUserTestAccountManagerCheckAccessThrowsPermissionDeniedException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); Mockito.doReturn(true).when(accountManager).isRootAdmin(Mockito.anyLong()); Mockito.doReturn(userVmVoMock).when(userVmDao).findById(Mockito.anyLong()); @@ -3147,7 +3138,7 @@ public void moveVmToUserTestAccountManagerCheckAccessThrowsPermissionDeniedExcep public void executeStepsToChangeOwnershipOfVmTestUpdateVmNetworkThrowsInsufficientCapacityException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); try (MockedStatic ignored = mockStatic(UsageEventUtils.class)) { Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(userVmVoMock).getHypervisorType(); @@ -3158,7 +3149,7 @@ public void executeStepsToChangeOwnershipOfVmTestUpdateVmNetworkThrowsInsufficie Mockito.any()); Assert.assertThrows(CloudRuntimeException.class, () -> userVmManagerImpl.executeStepsToChangeOwnershipOfVm(assignVmCmdMock, callerAccount, accountMock, accountMock, - userVmVoMock, serviceOfferingVoMock, volumes, virtualMachineTemplateMock, 1l)); + userVmVoMock, serviceOfferingVoMock, volumes, virtualMachineTemplateMock, 1L)); Mockito.verify(userVmManagerImpl).resourceCountDecrement(Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.verify(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()); @@ -3170,7 +3161,7 @@ public void executeStepsToChangeOwnershipOfVmTestUpdateVmNetworkThrowsInsufficie public void executeStepsToChangeOwnershipOfVmTestUpdateVmNetworkThrowsResourceAllocationException() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); try (MockedStatic ignored = mockStatic(UsageEventUtils.class)) { Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(userVmVoMock).getHypervisorType(); @@ -3181,7 +3172,7 @@ public void executeStepsToChangeOwnershipOfVmTestUpdateVmNetworkThrowsResourceAl Mockito.any()); Assert.assertThrows(CloudRuntimeException.class, () -> userVmManagerImpl.executeStepsToChangeOwnershipOfVm(assignVmCmdMock, callerAccount, accountMock, accountMock, - userVmVoMock, serviceOfferingVoMock, volumes, virtualMachineTemplateMock, 1l)); + userVmVoMock, serviceOfferingVoMock, volumes, virtualMachineTemplateMock, 1L)); Mockito.verify(userVmManagerImpl).resourceCountDecrement(Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.verify(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()); @@ -3193,7 +3184,7 @@ public void executeStepsToChangeOwnershipOfVmTestUpdateVmNetworkThrowsResourceAl public void executeStepsToChangeOwnershipOfVmTestResourceCountRunningVmsOnlyEnabledIsFalseCallsResourceCountIncrement() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); try (MockedStatic ignored = mockStatic(UsageEventUtils.class)) { @@ -3217,7 +3208,7 @@ public void executeStepsToChangeOwnershipOfVmTestResourceCountRunningVmsOnlyEnab public void executeStepsToChangeOwnershipOfVmTestResourceCountRunningVmsOnlyEnabledIsTrueDoesNotCallResourceCountIncrement() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - LinkedList volumes = new LinkedList(); + LinkedList volumes = new LinkedList<>(); try (MockedStatic ignored = mockStatic(UsageEventUtils.class)) { Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(userVmVoMock).getHypervisorType(); @@ -3226,7 +3217,7 @@ public void executeStepsToChangeOwnershipOfVmTestResourceCountRunningVmsOnlyEnab configureDoNothingForMethodsThatWeDoNotWantToTest(); userVmManagerImpl.executeStepsToChangeOwnershipOfVm(assignVmCmdMock, callerAccount, accountMock, accountMock, userVmVoMock, serviceOfferingVoMock, volumes, - virtualMachineTemplateMock, 1l); + virtualMachineTemplateMock, 1L); Mockito.verify(userVmManagerImpl).resourceCountDecrement(Mockito.anyLong(), Mockito.any(), Mockito.any(), Mockito.any()); Mockito.verify(userVmManagerImpl).updateVmOwner(Mockito.any(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong()); @@ -3490,9 +3481,9 @@ public void testResetVMSSHKey() throws ResourceUnavailableException, Insufficien assertNotNull(result); Map details = result.getDetails(); - Assert.assertEquals(details.get(VmDetailConstants.SSH_PUBLIC_KEY), "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr...\n" + - "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr..."); - Assert.assertEquals(details.get(VmDetailConstants.SSH_KEY_PAIR_NAMES), "keypair1,keypair2"); + Assert.assertEquals("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr...\n" + + "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAr...", details.get(VmDetailConstants.SSH_PUBLIC_KEY)); + Assert.assertEquals("keypair1,keypair2", details.get(VmDetailConstants.SSH_KEY_PAIR_NAMES)); } @Test @@ -3888,15 +3879,38 @@ Map getLeaseDetails(int leaseDuration, String leaseExecution) { return leaseDetails; } + private static DeployVMCmd buildDeployVMCmd(Long volumeId, Long snapshotId) { + DeployVMCmd deployVMCmd = mock(DeployVMCmd.class); + when(deployVMCmd.getEntityOwnerId()).thenReturn(accountId); + when(deployVMCmd.getZoneId()).thenReturn(zoneId); + when(deployVMCmd.getServiceOfferingId()).thenReturn(serviceOfferingId); + when(deployVMCmd.getVolumeId()).thenReturn(volumeId); + when(deployVMCmd.getSnapshotId()).thenReturn(snapshotId); + when(deployVMCmd.getTemplateId()).thenReturn(null); + when(deployVMCmd.isVolumeOrSnapshotProvided()).thenReturn(true); + when(deployVMCmd.getNetworkIds()).thenReturn(null); + when(deployVMCmd.getSecurityGroupNameList()).thenReturn(null); + // Long methods default to 0L in Mockito; null is required to match real DeployVMCmd field-unset behaviour + when(deployVMCmd.getDiskOfferingId()).thenReturn(null); + when(deployVMCmd.getUserdataId()).thenReturn(null); + when(deployVMCmd.getHostId()).thenReturn(null); + // boolean method defaults to false; real DeployVMCmd returns true when field is null + when(deployVMCmd.isDynamicScalingEnabled()).thenReturn(true); + return deployVMCmd; + } + + private static DeployVMCmd getDeployVMCmdFromSnapshot() { + return buildDeployVMCmd(null, snashotId); + } + + private static DeployVMCmd getDeployVMCmdFromVolume() { + return buildDeployVMCmd(volumeId, null); + } + @Test public void createVirtualMachineWithExistingVolume() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - DeployVMCmd deployVMCmd = new DeployVMCmd(); - ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId); - ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId); - ReflectionTestUtils.setField(deployVMCmd, "volumeId", volumeId); - deployVMCmd._accountService = accountService; + DeployVMCmd deployVMCmd = getDeployVMCmdFromVolume(); - when(accountService.finalizeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId); when(accountService.getActiveAccountById(accountId)).thenReturn(account); when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock); when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering); @@ -3925,15 +3939,11 @@ public void createVirtualMachineWithExistingVolume() throws ResourceUnavailableE } @Test - public void createVirtualMachineWithExistingSnapshot() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - DeployVMCmd deployVMCmd = new DeployVMCmd(); - ReflectionTestUtils.setField(deployVMCmd, "zoneId", zoneId); - ReflectionTestUtils.setField(deployVMCmd, "serviceOfferingId", serviceOfferingId); - ReflectionTestUtils.setField(deployVMCmd, "snapshotId", snashotId); - deployVMCmd._accountService = accountService; + public void createVirtualMachineWithExistingSnapshot() + throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + DeployVMCmd deployVMCmd = getDeployVMCmdFromSnapshot(); // mock, no field hacks - when(accountService.finalizeAccountId(nullable(String.class), nullable(Long.class), nullable(Long.class), eq(true))).thenReturn(accountId); - when(accountService.getActiveAccountById(accountId)).thenReturn(account); + when(accountService.getActiveAccountById(accountId)).thenReturn(account); // keep when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock); when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering); when(entityManager.findById(DiskOffering.class, serviceOffering.getId())).thenReturn(smallerDisdkOffering); @@ -3943,9 +3953,6 @@ public void createVirtualMachineWithExistingSnapshot() throws ResourceUnavailabl when(snapshotMock.getVolumeId()).thenReturn(volumeId); when(volumeInfo.getTemplateId()).thenReturn(templateId); when(volumeInfo.getInstanceId()).thenReturn(null); - when(volumeInfo.getDataStore()).thenReturn(primaryDataStore); - when(primaryDataStore.getScope()).thenReturn(scopeMock); - when(primaryDataStore.getScope().getScopeType()).thenReturn(ScopeType.ZONE); when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF); when(templateMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); when(templateMock.isDeployAsIs()).thenReturn(false); @@ -3958,10 +3965,103 @@ public void createVirtualMachineWithExistingSnapshot() throws ResourceUnavailabl any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), any(), any(), any(), any(), eq(true), any(), any(), any()); + userVmManagerImpl.createVirtualMachine(deployVMCmd); + } + + @Test + public void createVirtualMachineWithSnapshotFromExpungedLocalStorageVolumeSucceeds() + throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + DeployVMCmd deployVMCmd = getDeployVMCmdFromSnapshot(); // mock, no field hacks + + when(accountService.getActiveAccountById(accountId)).thenReturn(account); + when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock); + when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering); + when(snapshotDaoMock.findById(snashotId)).thenReturn(snapshotMock); + when(snapshotMock.getVolumeId()).thenReturn(volumeId); + when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo); + when(volumeInfo.getTemplateId()).thenReturn(templateId); + when(volumeInfo.getInstanceId()).thenReturn(null); + when(entityManager.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock); + when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + when(templateMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateMock.isDeployAsIs()).thenReturn(false); + when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); + when(templateMock.getUserDataId()).thenReturn(null); + Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class), any()); + when(_dcMock.isLocalStorageEnabled()).thenReturn(true); + when(_dcMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); + Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(true), any(), any(), any()); + + // Must NOT throw "Deployment of virtual machine is supported only for Zone-wide storage pools" + userVmManagerImpl.createVirtualMachine(deployVMCmd); + } + + @Test + public void createVirtualMachineWithSnapshotFromVolumeWithNullDataStoreSucceeds() + throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { + DeployVMCmd deployVMCmd = getDeployVMCmdFromSnapshot(); // mock, no field hacks + when(accountService.getActiveAccountById(accountId)).thenReturn(account); + when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock); + when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering); + when(snapshotDaoMock.findById(snashotId)).thenReturn(snapshotMock); + when(snapshotMock.getVolumeId()).thenReturn(volumeId); + when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo); + when(volumeInfo.getTemplateId()).thenReturn(templateId); + when(volumeInfo.getInstanceId()).thenReturn(null); + when(entityManager.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(templateMock); + when(templateMock.getTemplateType()).thenReturn(Storage.TemplateType.VNF); + when(templateMock.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateMock.isDeployAsIs()).thenReturn(false); + when(templateMock.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); + when(templateMock.getUserDataId()).thenReturn(null); + Mockito.doNothing().when(vnfTemplateManager).validateVnfApplianceNics(any(), nullable(List.class), any()); + when(_dcMock.isLocalStorageEnabled()).thenReturn(true); + when(_dcMock.getNetworkType()).thenReturn(DataCenter.NetworkType.Basic); + Mockito.doReturn(userVmVoMock).when(userVmManagerImpl).createBasicSecurityGroupVirtualMachine(any(), any(), any(), any(), any(), any(), any(), + any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), nullable(Boolean.class), any(), any(), any(), + any(), any(), any(), any(), eq(true), any(), any(), any()); + + // Must NOT throw "Deployment of virtual machine is supported only for Zone-wide storage pools" userVmManagerImpl.createVirtualMachine(deployVMCmd); } + @Test + public void createVirtualMachineWithVolumeFromNonZoneScopedStorageFails() { + DeployVMCmd deployVMCmd = getDeployVMCmdFromVolume(); + + when(accountService.getActiveAccountById(accountId)).thenReturn(account); + when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock); + when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering); + when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo); + // Volume lives on HOST-scoped (local) storage + when(volumeInfo.getDataStore()).thenReturn(primaryDataStore); + when(primaryDataStore.getScope()).thenReturn(scopeMock); + when(scopeMock.getScopeType()).thenReturn(ScopeType.HOST); + + InvalidParameterValueException ex = assertThrows(InvalidParameterValueException.class, + () -> userVmManagerImpl.createVirtualMachine(deployVMCmd)); + assertEquals("Deployment of virtual machine is supported only for Zone-wide storage pools", ex.getMessage()); + } + + @Test + public void createVirtualMachineWithVolumeWithNullDataStoreFails() { + DeployVMCmd deployVMCmd = getDeployVMCmdFromVolume(); + + when(accountService.getActiveAccountById(accountId)).thenReturn(account); + when(entityManager.findById(DataCenter.class, zoneId)).thenReturn(_dcMock); + when(entityManager.findById(ServiceOffering.class, serviceOfferingId)).thenReturn(serviceOffering); + when(volumeDataFactory.getVolume(volumeId)).thenReturn(volumeInfo); + // Volume's data store is null (pool was deleted) + when(volumeInfo.getDataStore()).thenReturn(null); + + InvalidParameterValueException ex = assertThrows(InvalidParameterValueException.class, + () -> userVmManagerImpl.createVirtualMachine(deployVMCmd)); + assertEquals("Deployment of virtual machine is supported only for Zone-wide storage pools", ex.getMessage()); + } + @Test public void testAllocateVMFromBackupWithVmSettingsRestoration() throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException { Long backupId = 10L; diff --git a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java index 3b120b88db45..e4bc2096b9da 100644 --- a/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java +++ b/server/src/test/java/com/cloud/vpc/MockNetworkManagerImpl.java @@ -704,11 +704,19 @@ public Network createPrivateNetwork(final long networkOfferingId, final String n public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, - String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException, ResourceAllocationException { + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize) throws ConcurrentOperationException { // TODO Auto-generated method stub return null; } + @Override + public Network createGuestNetwork(long networkOfferingId, String name, String displayText, String gateway, String cidr, String vlanId, boolean bypassVlanOverlapCheck, String networkDomain, + Account owner, Long domainId, PhysicalNetwork physicalNetwork, long zoneId, ACLType aclType, Boolean subdomainAccess, Long vpcId, String gatewayv6, + String cidrv6, Boolean displayNetworkEnabled, String isolatedPvlan, Network.PVlanType isolatedPvlanType, String externalId, String routerIp, String routerIpv6, + String ip4Dns1, String ip4Dns2, String ip6Dns1, String ip6Dns2, Pair vrIfaceMTUs, Integer networkCidrSize, boolean keepMacAddressOnPublicNic) throws ConcurrentOperationException { + return null; + } + /* (non-Javadoc) * @see com.cloud.network.NetworkManager#getPasswordResetProvider(com.cloud.network.Network) */ diff --git a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java index 8094bddf47fb..27dd630e470f 100644 --- a/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java +++ b/server/src/test/java/org/apache/cloudstack/backup/BackupManagerTest.java @@ -578,7 +578,7 @@ public void tryRestoreVMTestRestoreFails() throws NoTransitionException { backupManager.tryRestoreVM(backup, vm, offering, "Checking message error."); fail("An exception is needed."); } catch (CloudRuntimeException e) { - assertEquals("Error restoring VM from backup [Checking message error.].", e.getMessage()); + assertEquals("Error restoring Instance from Backup [Checking message error.].", e.getMessage()); } } } @@ -2215,7 +2215,7 @@ public void testRestoreBackupVmNotStopped() { CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, () -> backupManager.restoreBackup(backupId)); - assertEquals("Existing VM should be stopped before being restored from backup", exception.getMessage()); + assertEquals("Existing Instance should be stopped before being restored from Backup", exception.getMessage()); verify(backupDao, times(1)).findById(backupId); verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); } @@ -2252,7 +2252,7 @@ public void testRestoreBackupVolumeMismatch() { CloudRuntimeException exception = Assert.assertThrows(CloudRuntimeException.class, () -> backupManager.restoreBackup(backupId)); - assertEquals("Unable to restore VM with the current backup as the backup has different number of disks as the VM", exception.getMessage()); + assertEquals("Unable to restore Instance with the current Backup as the Backup has different number of disks to the Instance", exception.getMessage()); } verify(backupDao, times(1)).findById(backupId); verify(vmInstanceDao, times(1)).findByIdIncludingRemoved(vmId); diff --git a/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java b/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java index 691bd882c078..7717e6427662 100644 --- a/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java +++ b/server/src/test/java/org/apache/cloudstack/ca/CABackgroundTaskTest.java @@ -115,19 +115,19 @@ public void testAutoRenewalEnabledWithNoExceptionsOnProvisioning() throws Except certMap.put(hostIp, expiredCertificate); Assume.assumeThat(certMap.size() == 1, is(true)); task.runInContext(); - Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null); + Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null, false); Mockito.verify(caManager, Mockito.times(0)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString()); } @Test public void testAutoRenewalEnabledWithExceptionsOnProvisioning() throws Exception { overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "true"); - Mockito.when(caManager.provisionCertificate(any(Host.class), anyBoolean(), nullable(String.class))).thenThrow(new CloudRuntimeException("some error")); + Mockito.when(caManager.provisionCertificate(any(Host.class), anyBoolean(), nullable(String.class), anyBoolean())).thenThrow(new CloudRuntimeException("some error")); host.setManagementServerId(ManagementServerNode.getManagementServerId()); certMap.put(hostIp, expiredCertificate); Assume.assumeThat(certMap.size() == 1, is(true)); task.runInContext(); - Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null); + Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null, false); Mockito.verify(caManager, Mockito.times(1)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString()); } @@ -138,12 +138,12 @@ public void testAutoRenewalDisabled() throws Exception { Assume.assumeThat(certMap.size() == 1, is(true)); // First round task.runInContext(); - Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), anyBoolean(), Mockito.anyString()); + Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), anyBoolean(), Mockito.anyString(), Mockito.anyBoolean()); Mockito.verify(caManager, Mockito.times(1)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString()); Mockito.reset(caManager); // Second round task.runInContext(); - Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), anyBoolean(), Mockito.anyString()); + Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), anyBoolean(), Mockito.anyString(), Mockito.anyBoolean()); Mockito.verify(caManager, Mockito.times(0)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString()); } diff --git a/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java index 08fa5529996a..2d60833d35c7 100644 --- a/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/ca/CAManagerImplTest.java @@ -24,6 +24,7 @@ import com.cloud.certificate.dao.CrlDao; import com.cloud.host.Host; import com.cloud.host.dao.HostDao; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.framework.ca.CAProvider; import org.apache.cloudstack.framework.ca.Certificate; @@ -33,16 +34,34 @@ 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.Spy; import org.mockito.junit.MockitoJUnitRunner; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.math.BigInteger; import java.security.KeyPair; import java.security.cert.X509Certificate; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.HashMap; + + +import org.mockito.MockedStatic; +import org.mockito.MockedConstruction; +import com.cloud.utils.ssh.SSHCmdHelper; +import com.cloud.host.HostVO; +import com.cloud.vm.VMInstanceVO; +import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.trilead.ssh2.Connection; +import com.cloud.agent.api.routing.NetworkElementCommand; +import org.apache.cloudstack.api.ApiConstants; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -62,8 +81,15 @@ public class CAManagerImplTest { private AgentManager agentManager; @Mock private CAProvider caProvider; - - private CAManagerImpl caManager; + @Mock + private VMInstanceDao vmInstanceDao; + @Mock + private NetworkOrchestrationService networkOrchestrationService; + @Mock + private ConfigurationDao configDao; + @InjectMocks + @Spy + private CAManagerImpl caManager = new CAManagerImpl(); private void addField(final CAManagerImpl provider, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException { Field f = CAManagerImpl.class.getDeclaredField(name); @@ -73,10 +99,6 @@ private void addField(final CAManagerImpl provider, final String name, final Obj @Before public void setUp() throws Exception { - caManager = new CAManagerImpl(); - addField(caManager, "crlDao", crlDao); - addField(caManager, "hostDao", hostDao); - addField(caManager, "agentManager", agentManager); addField(caManager, "configuredCaProvider", caProvider); Mockito.when(caProvider.getProviderName()).thenReturn("root"); @@ -91,19 +113,19 @@ public void tearDown() throws Exception { } @Test(expected = ServerApiException.class) - public void testIssueCertificateThrowsException() throws Exception { + public void testIssueCertificateThrowsException() { caManager.issueCertificate(null, null, null, 1, null); } @Test - public void testIssueCertificate() throws Exception { + public void testIssueCertificate() { caManager.issueCertificate(null, Collections.singletonList("domain.example"), null, 1, null); Mockito.verify(caProvider, Mockito.times(1)).issueCertificate(anyList(), nullable(List.class), anyInt()); Mockito.verify(caProvider, Mockito.times(0)).issueCertificate(anyString(), anyList(), anyList(), anyInt()); } @Test - public void testRevokeCertificate() throws Exception { + public void testRevokeCertificate() { final CrlVO crl = new CrlVO(CertUtils.generateRandomBigInt(), "some.domain", "some-uuid"); Mockito.when(crlDao.revokeCertificate(Mockito.any(BigInteger.class), anyString())).thenReturn(crl); Mockito.when(caProvider.revokeCertificate(Mockito.any(BigInteger.class), anyString())).thenReturn(true); @@ -121,9 +143,190 @@ public void testProvisionCertificate() throws Exception { Mockito.when(agentManager.send(anyLong(), any(SetupCertificateCommand.class))).thenReturn(new SetupCertificateAnswer(true)); Mockito.when(agentManager.send(anyLong(), any(SetupKeyStoreCommand.class))).thenReturn(new SetupKeystoreAnswer("someCsr")); Mockito.doNothing().when(agentManager).reconnect(Mockito.anyLong()); - Assert.assertTrue(caManager.provisionCertificate(host, true, null)); + Assert.assertTrue(caManager.provisionCertificate(host, true, null, false)); Mockito.verify(agentManager, Mockito.times(1)).send(Mockito.anyLong(), any(SetupKeyStoreCommand.class)); Mockito.verify(agentManager, Mockito.times(1)).send(Mockito.anyLong(), any(SetupCertificateCommand.class)); Mockito.verify(agentManager, Mockito.times(1)).reconnect(Mockito.anyLong()); } + + + @Test + public void testProvisionCertificateForced() throws Exception { + final Host host = Mockito.mock(Host.class); + Mockito.doReturn(true).when(caManager).provisionCertificateForced(host, true, null); + Assert.assertTrue(caManager.provisionCertificate(host, true, null, true)); + Mockito.verify(caManager, Mockito.times(1)).provisionCertificateForced(host, true, null); + Mockito.verify(agentManager, Mockito.never()).send(Mockito.anyLong(), any(SetupKeyStoreCommand.class)); + Mockito.verify(agentManager, Mockito.never()).send(Mockito.anyLong(), any(SetupCertificateCommand.class)); + } + + @Test + public void testIssueCertificateWithCsr() throws Exception { + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate x509 = CertUtils.generateV3Certificate(null, keyPair, keyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null); + Mockito.when(caProvider.issueCertificate(anyString(), anyList(), anyList(), anyInt())) + .thenReturn(new Certificate(x509, null, Collections.singletonList(x509))); + final Certificate result = caManager.issueCertificate("someCsr", Collections.singletonList("domain.example"), Collections.singletonList("1.2.3.4"), 365, null); + Assert.assertNotNull(result); + Mockito.verify(caProvider, Mockito.times(1)).issueCertificate(anyString(), anyList(), anyList(), anyInt()); + Mockito.verify(caProvider, Mockito.never()).issueCertificate(anyList(), nullable(List.class), anyInt()); + } + + @Test(expected = CloudRuntimeException.class) + public void testProvisionCertificateNullHost() { + caManager.provisionCertificate(null, true, null, false); + } + + @Test + public void testProvisionCertificateForSystemVm() throws Exception { + final Host host = Mockito.mock(Host.class); + Mockito.when(host.getType()).thenReturn(Host.Type.ConsoleProxy); + Mockito.when(host.getPrivateIpAddress()).thenReturn("1.2.3.4"); + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate x509 = CertUtils.generateV3Certificate(null, keyPair, keyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null); + Mockito.when(caProvider.issueCertificate(anyList(), anyList(), anyInt())) + .thenReturn(new Certificate(x509, null, Collections.singletonList(x509))); + Mockito.when(agentManager.send(anyLong(), any(SetupCertificateCommand.class))).thenReturn(new SetupCertificateAnswer(true)); + Assert.assertTrue(caManager.provisionCertificate(host, false, null, false)); + Mockito.verify(agentManager, Mockito.never()).send(Mockito.anyLong(), any(SetupKeyStoreCommand.class)); + Mockito.verify(agentManager, Mockito.times(1)).send(Mockito.anyLong(), any(SetupCertificateCommand.class)); + Mockito.verify(agentManager, Mockito.never()).reconnect(Mockito.anyLong()); + } + + @Test + public void testProvisionCertificateWithoutReconnect() throws Exception { + final Host host = Mockito.mock(Host.class); + Mockito.when(host.getPrivateIpAddress()).thenReturn("1.2.3.4"); + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate x509 = CertUtils.generateV3Certificate(null, keyPair, keyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null); + Mockito.when(caProvider.issueCertificate(anyString(), anyList(), anyList(), anyInt())) + .thenReturn(new Certificate(x509, null, Collections.singletonList(x509))); + Mockito.when(agentManager.send(anyLong(), any(SetupCertificateCommand.class))).thenReturn(new SetupCertificateAnswer(true)); + Mockito.when(agentManager.send(anyLong(), any(SetupKeyStoreCommand.class))).thenReturn(new SetupKeystoreAnswer("someCsr")); + Assert.assertTrue(caManager.provisionCertificate(host, false, null, false)); + Mockito.verify(agentManager, Mockito.never()).reconnect(Mockito.anyLong()); + } + + @Test + public void testRevokeCertificateReturnsFalseWhenCrlIsNull() { + Mockito.when(crlDao.revokeCertificate(Mockito.any(BigInteger.class), anyString())).thenReturn(null); + Assert.assertFalse(caManager.revokeCertificate(BigInteger.ONE, "some.domain", null)); + Mockito.verify(caProvider, Mockito.never()).revokeCertificate(Mockito.any(BigInteger.class), anyString()); + } + + @Test + public void testRevokeCertificateReturnsFalseWhenSerialMismatch() { + final CrlVO crl = new CrlVO(BigInteger.ONE, "some.domain", "some-uuid"); + Mockito.when(crlDao.revokeCertificate(Mockito.any(BigInteger.class), anyString())).thenReturn(crl); + Assert.assertFalse(caManager.revokeCertificate(BigInteger.TWO, "some.domain", null)); + Mockito.verify(caProvider, Mockito.never()).revokeCertificate(Mockito.any(BigInteger.class), anyString()); + } + + @Test + public void testPurgeHostCertificate() throws Exception { + final Host host = Mockito.mock(Host.class); + Mockito.when(host.getPrivateIpAddress()).thenReturn("10.0.0.1"); + Mockito.when(host.getPublicIpAddress()).thenReturn("192.168.0.1"); + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate x509 = CertUtils.generateV3Certificate(null, keyPair, + keyPair.getPublic(), "CN=ca", "SHA256withRSA", + 365, null, null); + caManager.getActiveCertificatesMap().put("10.0.0.1", x509); + caManager.getActiveCertificatesMap().put("192.168.0.1", x509); + caManager.purgeHostCertificate(host); + Assert.assertFalse(caManager.getActiveCertificatesMap().containsKey("10.0.0.1")); + Assert.assertFalse(caManager.getActiveCertificatesMap().containsKey("192.168.0.1")); + } + @Test + public void testProvisionCertificateViaSsh() throws Exception { + Connection sshConnection = Mockito.mock(Connection.class); + final String agentIp = "192.168.1.1"; + final String agentHostname = "host1"; + final String caProviderStr = "root"; + + try (MockedStatic sshCmdHelperMock = Mockito.mockStatic(SSHCmdHelper.class)) { + SSHCmdHelper.SSHCmdResult successResult = new SSHCmdHelper.SSHCmdResult(0, "someCsr", ""); + sshCmdHelperMock.when(() -> SSHCmdHelper.sshExecuteCmdWithResult(Mockito.eq(sshConnection), Mockito.anyString())) + .thenReturn(successResult); + + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate x509 = CertUtils.generateV3Certificate(null, keyPair, keyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null); + Mockito.doReturn(new Certificate(x509, null, Collections.singletonList(x509))) + .when(caManager).issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.nullable(Integer.class), Mockito.anyString()); + + caManager.provisionCertificateViaSsh(sshConnection, agentIp, agentHostname, caProviderStr); + + sshCmdHelperMock.verify(() -> SSHCmdHelper.sshExecuteCmdWithResult(Mockito.eq(sshConnection), Mockito.contains("keystore-setup")), Mockito.times(1)); + sshCmdHelperMock.verify(() -> SSHCmdHelper.sshExecuteCmdWithResult(Mockito.eq(sshConnection), Mockito.contains("keystore-cert-import")), Mockito.times(1)); + } + } + + @Test + public void testProvisionKvmHostViaSsh() throws Exception { + HostVO host = Mockito.mock(HostVO.class); + Mockito.when(host.getPrivateIpAddress()).thenReturn("192.168.1.1"); + Mockito.when(host.getName()).thenReturn("host1"); + Mockito.when(host.getClusterId()).thenReturn(1L); + + Mockito.doNothing().when(hostDao).loadDetails(host); + Mockito.when(host.getDetail(ApiConstants.USERNAME)).thenReturn("root"); + Mockito.when(host.getDetail(ApiConstants.PASSWORD)).thenReturn("password"); + + Mockito.when(configDao.getValue("ssh.privatekey")).thenReturn("privatekey"); + + try (MockedConstruction ignored = Mockito.mockConstruction(Connection.class, + (mock, context) -> { + // Do nothing on connect + }); + MockedStatic sshCmdHelperMock = Mockito.mockStatic(SSHCmdHelper.class)) { + sshCmdHelperMock.when(() -> SSHCmdHelper.acquireAuthorizedConnectionWithPublicKey(Mockito.any(Connection.class), Mockito.anyString(), Mockito.anyString())) + .thenReturn(true); + + Mockito.doNothing().when(caManager).provisionCertificateViaSsh(Mockito.any(Connection.class), Mockito.anyString(), Mockito.anyString(), Mockito.anyString()); + + Method method = CAManagerImpl.class.getDeclaredMethod("provisionKvmHostViaSsh", Host.class, String.class); + method.setAccessible(true); + boolean result = (Boolean) method.invoke(caManager, host, "root"); + + Assert.assertTrue(result); + Mockito.verify(caManager, Mockito.times(1)).provisionCertificateViaSsh(Mockito.any(Connection.class), Mockito.eq("192.168.1.1"), Mockito.eq("host1"), Mockito.eq("root")); + sshCmdHelperMock.verify(() -> SSHCmdHelper.sshExecuteCmd(Mockito.any(Connection.class), Mockito.eq("systemctl restart libvirtd")), Mockito.times(1)); + sshCmdHelperMock.verify(() -> SSHCmdHelper.sshExecuteCmd(Mockito.any(Connection.class), Mockito.eq("systemctl restart cloudstack-agent")), Mockito.times(1)); + } + } + + @Test + public void testProvisionSystemVmViaSsh() throws Exception { + Host host = Mockito.mock(Host.class); + Mockito.when(host.getName()).thenReturn("v-1-VM"); + + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + Mockito.when(vm.getHostId()).thenReturn(1L); + Mockito.when(vm.getHostName()).thenReturn("host1"); + Mockito.when(vm.getInstanceName()).thenReturn("v-1-VM"); + Mockito.when(vmInstanceDao.findVMByInstanceName("v-1-VM")).thenReturn(vm); + + Map accessDetails = new HashMap<>(); + accessDetails.put(NetworkElementCommand.ROUTER_IP, "192.168.1.2"); + Mockito.when(networkOrchestrationService.getSystemVMAccessDetails(vm)).thenReturn(accessDetails); + + HostVO hypervisorHost = Mockito.mock(HostVO.class); + Mockito.when(hostDao.findById(1L)).thenReturn(hypervisorHost); + + final KeyPair keyPair = CertUtils.generateRandomKeyPair(1024); + final X509Certificate x509 = CertUtils.generateV3Certificate(null, keyPair, keyPair.getPublic(), "CN=ca", "SHA256withRSA", 365, null, null); + Certificate cert = new Certificate(x509, null, Collections.singletonList(x509)); + Mockito.doReturn(cert) + .when(caManager).issueCertificate(Mockito.nullable(String.class), Mockito.anyList(), Mockito.anyList(), Mockito.nullable(Integer.class), Mockito.anyString()); + + Mockito.doReturn(true) + .when(caManager).deployCertificate(Mockito.eq(hypervisorHost), Mockito.eq(cert), Mockito.anyBoolean(), Mockito.eq(accessDetails)); + + Method method = CAManagerImpl.class.getDeclaredMethod("provisionSystemVmViaSsh", Host.class, Boolean.class, String.class); + method.setAccessible(true); + boolean result = (Boolean) method.invoke(caManager, host, true, "root"); + + Assert.assertTrue(result); + Mockito.verify(caManager, Mockito.times(1)).deployCertificate(Mockito.eq(hypervisorHost), Mockito.eq(cert), Mockito.eq(true), Mockito.eq(accessDetails)); + } } diff --git a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java index 874bce0f95ef..bee6c4ad257f 100644 --- a/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java +++ b/server/src/test/java/org/apache/cloudstack/vm/UnmanagedVMsManagerImplTest.java @@ -169,10 +169,10 @@ import com.cloud.vm.NicProfile; import com.cloud.vm.UserVmManager; import com.cloud.vm.UserVmVO; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VmDetailConstants; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; diff --git a/services/console-proxy/rdpconsole/pom.xml b/services/console-proxy/rdpconsole/pom.xml index 058ea891f9cd..9e722be37a09 100644 --- a/services/console-proxy/rdpconsole/pom.xml +++ b/services/console-proxy/rdpconsole/pom.xml @@ -46,11 +46,11 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on org.bouncycastle - bctls-jdk15on + bctls-jdk18on com.sun.xml.security diff --git a/setup/db/deploy-db-dev.sh b/setup/db/deploy-db-dev.sh index 7896276f8f97..4e0814e0c3f1 100755 --- a/setup/db/deploy-db-dev.sh +++ b/setup/db/deploy-db-dev.sh @@ -104,9 +104,10 @@ CP=./ CP=${CP}$PATHSEP$CATALINA_HOME/conf -# Add mysql jar from mysql-connector-java package to CP +# Add mysql jar from mysql-connector-j package to CP # for Jenkins -CP=${CP}${PATHSEP}/usr/share/java/mysql-connector-java.jar +MYSQL_CONNECTOR_VERSION = '8.4.0' +CP=${CP}${PATHSEP}/usr/share/java/mysql-connector-j-${MYSQL_CONNECTOR_VERSION}.jar for file in $CATALINA_HOME/webapps/client/WEB-INF/lib/*.jar do diff --git a/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py b/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py index 93d0d0388efe..63d7724dd20a 100755 --- a/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py +++ b/systemvm/debian/opt/cloud/bin/cs/CsNetfilter.py @@ -232,7 +232,7 @@ def add_ip6_chain(self, address_family, table, chain, hook, action): if hook == "input" or hook == "output": CsHelper.execute("nft add rule %s %s %s icmpv6 type { echo-request, echo-reply, \ nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept" % (address_family, table, chain)) - elif hook == "forward": + if hook == "input" or hook == "forward": CsHelper.execute("nft add rule %s %s %s ct state established,related accept" % (address_family, table, chain)) def add_ip4_chain(self, address_family, table, chain, hook, action): diff --git a/systemvm/patch-sysvms.sh b/systemvm/patch-sysvms.sh index 88d720e0f32f..8d96de9ba3b8 100755 --- a/systemvm/patch-sysvms.sh +++ b/systemvm/patch-sysvms.sh @@ -126,7 +126,28 @@ patch_systemvm() { if [ "$TYPE" = "consoleproxy" ] || [ "$TYPE" = "secstorage" ]; then # Import global cacerts into 'cloud' service's keystore - keytool -importkeystore -srckeystore /etc/ssl/certs/java/cacerts -destkeystore /usr/local/cloud/systemvm/certs/realhostip.keystore -srcstorepass changeit -deststorepass vmops.com -noprompt 2>/dev/null || true + REALHOSTIP_KS_FILE="/usr/local/cloud/systemvm/certs/realhostip.keystore" + REALHOSTIP_PASS="vmops.com" + + keytool -importkeystore -srckeystore /etc/ssl/certs/java/cacerts \ + -destkeystore "$REALHOSTIP_KS_FILE" -srcstorepass changeit -deststorepass \ + "$REALHOSTIP_PASS" -noprompt 2>/dev/null || true + + # Import CA cert(s) into realhostip.keystore so the SSVM JVM + # (which overrides the truststore via -Djavax.net.ssl.trustStore in _run.sh) + # can trust servers signed by the CloudStack CA + CACERT_FILE="/usr/local/share/ca-certificates/cloudstack/ca.crt" + + if [ -f "$CACERT_FILE" ] && [ -f "$REALHOSTIP_KS_FILE" ]; then + awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE" + for caChain in $(ls cloudca.* 2>/dev/null); do + keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \ + -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true + keytool -import -noprompt -trustcacerts -alias "$caChain" -file "$caChain" \ + -keystore "$REALHOSTIP_KS_FILE" -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 + done + rm -f cloudca.* + fi fi update_checksum $newpath/cloud-scripts.tgz diff --git a/test/integration/component/test_escalations_instances.py b/test/integration/component/test_escalations_instances.py index 89c4f4ce2a6c..4aea35850d07 100644 --- a/test/integration/component/test_escalations_instances.py +++ b/test/integration/component/test_escalations_instances.py @@ -3341,9 +3341,6 @@ def test_17_running_vm_scaleup(self): deployed in step1 Step6: Verifying that VM's service offerings is changed """ - if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") # Checking if Dynamic scaling of VM is supported or not list_config = Configurations.list( self.apiClient, diff --git a/test/integration/component/test_escalations_ipaddresses.py b/test/integration/component/test_escalations_ipaddresses.py index b91b67c16c3d..d9d760c49017 100644 --- a/test/integration/component/test_escalations_ipaddresses.py +++ b/test/integration/component/test_escalations_ipaddresses.py @@ -3455,8 +3455,7 @@ def test_17_create_update_autoscalepolicy(self): Step18: Verifying Autoscale policy is updated with condition2 """ if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") + self.skipTest("Test not supported on KVM. Skipping it") list_physical_networks = PhysicalNetwork.list( self.apiClient, @@ -3734,8 +3733,7 @@ def test_18_create_update_autoscaleprofiles(self): Step16: Verifying that Autoscale VM is updated """ if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") + self.skipTest("Test not supported on KVM. Skipping it") list_physical_networks = PhysicalNetwork.list( self.apiClient, @@ -4061,8 +4059,7 @@ def test_19_create_update_autoscalevmgroup(self): Step14: Enabling Autoscale VM group and verifying it was enabled """ if self.hypervisor.lower() == 'kvm': - self.skipTest( - "ScaleVM is not supported on KVM. Hence, skipping the test") + self.skipTest("Test not supported on KVM. Skipping it.") list_physical_networks = PhysicalNetwork.list( self.apiClient, diff --git a/test/integration/plugins/quota/test_quota_balance.py b/test/integration/plugins/quota/test_quota_balance.py index f5c1c75d7b23..ca702bbda013 100644 --- a/test/integration/plugins/quota/test_quota_balance.py +++ b/test/integration/plugins/quota/test_quota_balance.py @@ -117,9 +117,28 @@ def tearDown(self): def delete_tariffs(self): for tariff in self.tariffs: cmd = quotaTariffDelete.quotaTariffDeleteCmd() - cmd.id = tariff.uuid + cmd.id = tariff.id self.apiclient.quotaTariffDelete(cmd) + def insert_usage_and_update_quota(self, zone_id, account_id, domain_id, relative_start_date, relative_end_date): + start_date = f"DATE_ADD(UTC_TIMESTAMP(), INTERVAL {relative_start_date} HOUR)" + end_date = f"DATE_ADD(UTC_TIMESTAMP(), INTERVAL {relative_end_date} HOUR)" + + # Manually insert a usage regarding the usage type 21 (VM_DISK_IO_READ) + sql_query = (f"INSERT INTO cloud_usage.cloud_usage (zone_id,account_id,domain_id,description,usage_display,usage_type,raw_usage,vm_instance_id,vm_name,offering_id,template_id," + f"usage_id,`type`,`size`,network_id,start_date,end_date,virtual_size,cpu_speed,cpu_cores,memory,quota_calculated,is_hidden,state)" + f" VALUES ('{zone_id}','{account_id}','{domain_id}','Test','1 Hrs',21,1,NULL,NULL,NULL,NULL,NULL,'VirtualMachine',NULL,NULL,{start_date},{end_date},NULL,NULL,NULL,NULL,0,0,NULL);") + self.debug(sql_query) + self.dbclient.execute(sql_query) + + # Update quota to calculate the balance of the account + cmd = quotaUpdate.quotaUpdateCmd() + self.apiclient.quotaUpdate(cmd) + time.sleep(1) + + def format_date(self, date): + return date.strftime("%Y-%m-%d %H:%M:%S") + @attr(tags=["advanced", "smoke", "quota"], required_hardware="false") def test_quota_balance(self): """ @@ -146,6 +165,7 @@ def test_quota_balance(self): cmd.domainid = self.domain.id cmd.value = 100 self.apiclient.quotaCredits(cmd) + time.sleep(1) # Fetch account ID from account_uuid account_id_select = f"SELECT id FROM account WHERE uuid = '{self.account.id}';" @@ -165,27 +185,24 @@ def test_quota_balance(self): qresultset = self.dbclient.execute(zone_id_select) zone_id = qresultset[0][0] - start_date = datetime.datetime.now() + datetime.timedelta(seconds=1) - end_date = datetime.datetime.now() + datetime.timedelta(hours=1) - - # Manually insert a usage regarding the usage type 21 (VM_DISK_IO_READ) - sql_query = (f"INSERT INTO cloud_usage.cloud_usage (zone_id,account_id,domain_id,description,usage_display,usage_type,raw_usage,vm_instance_id,vm_name,offering_id,template_id," - f"usage_id,`type`,`size`,network_id,start_date,end_date,virtual_size,cpu_speed,cpu_cores,memory,quota_calculated,is_hidden,state)" - f" VALUES ('{zone_id}','{account_id}','{domain_id}','Test','1 Hrs',21,1,NULL,NULL,NULL,NULL,NULL,'VirtualMachine',NULL,NULL,'{start_date}','{end_date}',NULL,NULL,NULL,NULL,0,0,NULL);") - self.debug(sql_query) - self.dbclient.execute(sql_query) - - # Update quota to calculate the balance of the account - cmd = quotaUpdate.quotaUpdateCmd() - self.apiclient.quotaUpdate(cmd) + # Generate three quota_balance entries + self.insert_usage_and_update_quota(zone_id, account_id, domain_id, 0, 1) + self.insert_usage_and_update_quota(zone_id, account_id, domain_id, 1, 2) + self.insert_usage_and_update_quota(zone_id, account_id, domain_id, 2, 3) # Retrieve the quota balance of the account cmd = quotaBalance.quotaBalanceCmd() cmd.domainid = self.account.domainid cmd.account = self.account.name + cmd.startdate = datetime.datetime.now() + datetime.timedelta(hours=-1) + cmd.enddate = datetime.datetime.now() + datetime.timedelta(hours=3) response = self.apiclient.quotaBalance(cmd) - self.debug(f"The quota balance for the account {self.account.name} is {response.balance}.") - self.assertEqual(response.balance.startquota, 90, f"The `startQuota` response field is supposed to be 90 but was {response.balance.startquota}.") + self.assertTrue(len(response.balance.balances) == 4, f"Expected 4 balance entries for between {self.format_date(cmd.startdate)} " + + f"and {self.format_date(cmd.enddate)} but got {len(response.balance.balances)}.") + for i, balance in enumerate(response.balance.balances): + expected_balance = 100 - 10 * i + self.debug(f"The quota balance for the account {self.account.name} at {balance.date} was {balance.balance}.") + self.assertEqual(balance.balance, expected_balance, f"The `balance` field at {balance.date} is supposed to be {expected_balance} but was {balance.balance}.") return diff --git a/test/integration/smoke/test_certauthority_root.py b/test/integration/smoke/test_certauthority_root.py index dc6420d6369f..491b8abeb2e8 100644 --- a/test/integration/smoke/test_certauthority_root.py +++ b/test/integration/smoke/test_certauthority_root.py @@ -15,9 +15,12 @@ # specific language governing permissions and limitations # under the License. +import re +from datetime import datetime, timedelta + from nose.plugins.attrib import attr from marvin.cloudstackTestCase import cloudstackTestCase -from marvin.lib.utils import cleanup_resources +from marvin.lib.utils import cleanup_resources, wait_until from marvin.lib.base import * from marvin.lib.common import list_hosts @@ -60,6 +63,29 @@ def verifySignature(self, caCert, cert): except Exception as e: print(f"Certificate verification failed: {e}") + + def parseCertificateChain(self, pem): + """Split a PEM blob containing one or more certificates into a list of x509 objects.""" + certs = [] + matches = re.findall( + r'-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----', + pem, + re.DOTALL + ) + for match in matches: + certs.append(x509.load_pem_x509_certificate(match.encode(), default_backend())) + return certs + + + def assertSignatureValid(self, issuerCert, cert): + """Verify cert is signed by issuerCert; raise on failure.""" + issuerCert.public_key().verify( + cert.signature, + cert.tbs_certificate_bytes, + padding.PKCS1v15(), + cert.signature_hash_algorithm, + ) + def setUp(self): self.apiclient = self.testClient.getApiClient() self.dbclient = self.testClient.getDbConnection() @@ -224,3 +250,152 @@ def checkHostIsUp(hostId): self.assertTrue(len(hosts) == 1) else: self.fail("Failed to have systemvm host in Up state after cert provisioning") + + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_ca_certificate_chain_validity(self): + """ + Tests that listCaCertificate returns a valid certificate chain. + When an intermediate CA is configured, the response is a PEM blob + containing multiple certificates. Each non-root cert must be signed + by the next cert in the chain, and the final cert must be self-signed. + """ + pem = self.getCaCertificate() + self.assertTrue(len(pem) > 0) + + chain = self.parseCertificateChain(pem) + self.assertTrue(len(chain) >= 1, "Expected at least one certificate in CA chain") + + # Each non-root cert must be signed by the next cert in the chain + for i in range(len(chain) - 1): + child = chain[i] + parent = chain[i + 1] + self.assertEqual( + child.issuer, parent.subject, + f"Chain break: cert[{i}] issuer does not match cert[{i + 1}] subject" + ) + try: + self.assertSignatureValid(parent, child) + except Exception as e: + self.fail(f"Signature verification failed for chain link {i} -> {i + 1}: {e}") + + # The last cert in the chain must be self-signed (root CA) + root = chain[-1] + self.assertEqual( + root.issuer, root.subject, + "Final cert in CA chain is not self-signed" + ) + try: + self.assertSignatureValid(root, root) + except Exception as e: + self.fail(f"Root CA self-signature verification failed: {e}") + + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_issue_certificate_issuer_matches_ca(self): + """ + Tests that an issued certificate's issuer DN matches the subject DN + of the first cert in the returned CA chain, and that the signature + verifies against that cert's public key. + """ + cmd = issueCertificate.issueCertificateCmd() + cmd.domain = 'apache.org' + cmd.ipaddress = '10.1.1.1' + cmd.provider = 'root' + + response = self.apiclient.issueCertificate(cmd) + self.assertTrue(len(response.certificate) > 0) + self.assertTrue(len(response.cacertificates) > 0) + + leaf = x509.load_pem_x509_certificate(response.certificate.encode(), default_backend()) + caChain = self.parseCertificateChain(response.cacertificates) + self.assertTrue(len(caChain) >= 1, "Expected at least one CA certificate in response") + + # The issuing CA is the first cert in the returned chain (intermediate + # if an intermediate CA is configured, otherwise the root). + issuingCa = caChain[0] + self.assertEqual( + leaf.issuer, issuingCa.subject, + "Leaf certificate issuer does not match issuing CA subject" + ) + try: + self.assertSignatureValid(issuingCa, leaf) + except Exception as e: + self.fail(f"Leaf certificate signature does not verify against issuing CA: {e}") + + + @attr(tags=['advanced', 'simulator', 'basic', 'sg'], required_hardware=False) + def test_certificate_validity_period(self): + """ + Tests that an issued certificate has sensible validity bounds: + not_valid_before <= now <= not_valid_after, and validity duration + is at least 300 days (CloudStack default is 1 year). + """ + cmd = issueCertificate.issueCertificateCmd() + cmd.domain = 'apache.org' + cmd.provider = 'root' + + response = self.apiclient.issueCertificate(cmd) + self.assertTrue(len(response.certificate) > 0) + + cert = x509.load_pem_x509_certificate(response.certificate.encode(), default_backend()) + + # cryptography >= 42 prefers the *_utc variants; fall back for older versions. + notBefore = getattr(cert, 'not_valid_before_utc', None) or cert.not_valid_before + notAfter = getattr(cert, 'not_valid_after_utc', None) or cert.not_valid_after + + now = datetime.now(notBefore.tzinfo) if notBefore.tzinfo else datetime.utcnow() + self.assertTrue(notBefore <= now, f"Certificate not_valid_before {notBefore} is in the future") + self.assertTrue(now <= notAfter, f"Certificate not_valid_after {notAfter} is in the past") + + duration = notAfter - notBefore + self.assertTrue( + duration >= timedelta(days=300), + f"Certificate validity duration {duration} is less than expected minimum of 300 days" + ) + + + def getUpKVMHosts(self, hostId=None): + hosts = list_hosts( + self.apiclient, + type='Routing', + hypervisor='KVM', + state='Up', + resourcestate='Enabled', + id=hostId + ) + return hosts + + + @attr(tags=['advanced'], required_hardware=True) + def test_provision_certificate_kvm(self): + """ + Tests certificate provisioning on a KVM host. + Exercises the keystore-cert-import + cloud.jks provisioning flow + against a real agent. Skipped when no KVM hosts are available. + """ + if self.hypervisor.lower() != 'kvm': + raise self.skipTest("Hypervisor is not KVM, skipping test") + + hosts = self.getUpKVMHosts() + if not hosts or len(hosts) < 1: + raise self.skipTest("No Up KVM hosts found, skipping test") + + host = hosts[0] + + cmd = provisionCertificate.provisionCertificateCmd() + cmd.hostid = host.id + cmd.reconnect = True + cmd.provider = 'root' + + response = self.apiclient.provisionCertificate(cmd) + self.assertTrue(response.success) + + def checkHostIsUp(hostId): + hosts = self.getUpKVMHosts(hostId) + return (hosts is not None and len(hosts) > 0), hosts + + result, hosts = wait_until(2, 30, checkHostIsUp, host.id) + if not result: + self.fail("KVM host did not return to Up state after certificate provisioning") + self.assertEqual(len(hosts), 1) diff --git a/test/integration/smoke/test_quarantined_ips.py b/test/integration/smoke/test_quarantined_ips.py index 42349fd2a530..2469760da130 100644 --- a/test/integration/smoke/test_quarantined_ips.py +++ b/test/integration/smoke/test_quarantined_ips.py @@ -85,7 +85,7 @@ def setUp(self): self.services["root_admin"]["roletype"]) """ - Set public.ip.address.quarantine.duration to 60 minutes + Set public.ip.address.quarantine.duration to 1 minute """ update_configuration_cmd = updateConfiguration.updateConfigurationCmd() update_configuration_cmd.name = "public.ip.address.quarantine.duration" @@ -168,8 +168,7 @@ def test_only_owner_can_allocate_ip_in_quarantine_vpc(self): zoneid=self.zone.id, vpcid=root_vpc.id, ipaddress=ip_address) - self.assertIn(f"Failed to allocate public IP address [{ip_address}] as it is in quarantine.", - exception.exception.errorMsg) + self.assertIn("errorCode: 533", exception.exception.errorMsg) # Owner should be able to allocate its IP in quarantine public_ip = PublicIPAddress.create(self.domain_admin_apiclient, @@ -267,8 +266,7 @@ def test_only_owner_can_allocate_ip_in_quarantine_network(self): zoneid=self.zone.id, networkid=root_network.id, ipaddress=ip_address) - self.assertIn(f"Failed to allocate public IP address [{ip_address}] as it is in quarantine.", - exception.exception.errorMsg) + self.assertIn("errorCode: 533", exception.exception.errorMsg) # Owner should be able to allocate its IP in quarantine public_ip = PublicIPAddress.create(self.domain_admin_apiclient, diff --git a/test/integration/smoke/test_vm_strict_host_tags.py b/test/integration/smoke/test_vm_strict_host_tags.py index 2377e9a76185..aac3e1ea65f2 100644 --- a/test/integration/smoke/test_vm_strict_host_tags.py +++ b/test/integration/smoke/test_vm_strict_host_tags.py @@ -423,7 +423,7 @@ def test_02_restore_vm_strict_tags_failure(self): vm.restore(self.apiclient, templateid=self.template_t2.id, expunge=True) self.fail("VM should not be restored") except Exception as e: - self.assertTrue("Unable to start VM with specified id" in str(e)) + self.assertTrue("Unable to create a deployment for " in str(e)) class TestMigrateVMStrictTags(cloudstackTestCase): diff --git a/tools/docker/Dockerfile b/tools/docker/Dockerfile index fd813e077fd1..dcadfb3fead6 100644 --- a/tools/docker/Dockerfile +++ b/tools/docker/Dockerfile @@ -58,7 +58,7 @@ RUN mvn -Pdeveloper -Dsimulator -DskipTests clean install RUN find /var/lib/mysql -type f -exec touch {} \; && \ (/usr/bin/mysqld_safe &) && \ sleep 5; \ - mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by ''" --connect-expired-password; \ + mysql -e "ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password by ''" --connect-expired-password; \ mvn -Pdeveloper -pl developer -Ddeploydb; \ mvn -Pdeveloper -pl developer -Ddeploydb-simulator; \ MARVIN_FILE=`find /root/tools/marvin/dist/ -name "Marvin*.tar.gz"`; \ diff --git a/tools/marvin/setup.py b/tools/marvin/setup.py index 4694cecc8a53..e126622930bb 100644 --- a/tools/marvin/setup.py +++ b/tools/marvin/setup.py @@ -46,7 +46,7 @@ "marvin.sandbox.basic"], license="LICENSE.txt", install_requires=[ - "mysql-connector-python <= 8.0.30", + "mysql-connector-python <= 8.4.0", "requests >= 2.2.1", "paramiko >= 1.13.0", "nose >= 1.3.3", diff --git a/ui/public/locales/el_GR.json b/ui/public/locales/el_GR.json index bb90661d9d37..566e182b8fad 100644 --- a/ui/public/locales/el_GR.json +++ b/ui/public/locales/el_GR.json @@ -2270,8 +2270,6 @@ "message.error.enable.saml": "Δεν είναι δυνατή η εύρεση των id χρηστών για την ενεργοποίηση του Saml σύνδεση μιας φοράς, παρακαλούμε να το ενεργοποιήσετε με μη αυτόματο τρόπο.", "message.error.end.date.and.time": "Παρακαλώ, εισάγετε την τελική ημερομηνία και ώρα!", "message.error.endip": "Πληκτρολογήστε End IP", -"message.error.fixed.offering.kvm": "Δεν είναι δυνατό να κλιμωκαθούν οι εικονές μηχανές που χρησιμοποιούν τον επόπτη KVM με ένα σταθερό υπολογισμό προσφοράς υπηρεσίας.", -"message.error.fixed.offering.kvm": "Δεν είναι εφικτή η κλιμάκωση προς τα πάνω της εικονικής μηχανής ενώ ανήκει σε μία σταθερή προσφορά νέφους.", "message.error.gateway": "Πληκτρολογήστε Πύλη", "message.error.host.name": "Πληκτρολογήστε το όνομα του κεντρικού υπολογιστή", "message.error.host.password": "Πληκτρολογήστε τον κωδικό πρόσβασης κεντρικού υπολογιστή", diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json index 4a23b454252a..3a16d613f37a 100644 --- a/ui/public/locales/en.json +++ b/ui/public/locales/en.json @@ -795,6 +795,7 @@ "label.delete.ciscoasa1000v": "Delete CiscoASA1000v", "label.delete.ciscovnmc.resource": "Delete CiscoVNMC resource", "label.delete.condition": "Delete condition", +"label.delete.confirmation": "Enter the exact resource name to proceed with deletion", "label.delete.custom.action": "Delete Custom Action", "label.delete.dedicated.vlan.range": "Deleted dedicated VLAN/VNI range.", "label.delete.domain": "Delete domain", @@ -1433,6 +1434,7 @@ "label.javadistribution": "Java Runtime Distribution", "label.javaversion": "Java Runtime Version", "label.keep": "Keep", +"label.keep.mac.address.on.public.nic": "Use same MAC address for public NIC of VRs", "label.kernelversion": "Kernel Version", "label.key": "Key", "label.keyboard": "Keyboard language", @@ -3075,6 +3077,7 @@ "message.action.unmanage.volume": "Please confirm that you want to unmanage the Volume.", "message.action.unmanage.volumes": "Please confirm that you want to unmanage the Volumes.", "message.action.vmsnapshot.delete": "Please confirm that you want to delete this Instance Snapshot.
Please notice that the Instance will be paused before the Snapshot deletion, and resumed after deletion, if it runs on KVM.", +"message.action.vmsnapshot.disk-only.delete": "Please confirm that you want to delete this Instance Snapshot.", "message.activate.project": "Are you sure you want to activate this project?", "message.add.custom.action.parameters": "Parameters to be made available while running the custom action.", "message.add.egress.rule.failed": "Adding new egress rule failed.", @@ -3597,7 +3600,6 @@ "message.error.zone.name": "Please enter Zone name.", "message.error.zone.type": "Please select Zone type.", "message.error.linstor.resourcegroup": "Please enter the Linstor Resource-Group.", -"message.error.fixed.offering.kvm": "It's not possible to scale up Instances that utilize KVM hypervisor with a fixed compute offering.", "message.error.create.webhook.local.account": "Account must be provided for creating a Webhook with Local scope.", "message.error.create.webhook.name": "Name must be provided for creating a Webhook.", "message.error.create.webhook.payloadurl": "Payload URL must be provided for creating a Webhook.", @@ -4166,7 +4168,9 @@ "message.warn.importing.instance.without.nic": "WARNING: This Instance is being imported without NICs and many Network resources will not be available. Consider creating a NIC via vCenter before importing or as soon as the Instance is imported. For KVM host, allocate a NIC to Instance after import.", "message.warn.select.template": "Please select a Template for Registration.", "message.warn.zone.mtu.update": "Please note that this limit won't affect pre-existing Network's MTU settings", +"message.warn.vpc.offerings": "VPC offerings will only be shown if the selected account has at least one VPC.", "message.webhook.deliveries.time.filter": "Webhook deliveries list can be filtered based on date-time. Select 'Custom' for specifying start and end date range.", +"message.webhook.filter.add": "Webhook deliveries can be controlled using filters (currently by Event type). Please select the parameters to add to the applied filters list.", "message.zone.creation.complete": "Zone creation complete.", "message.zone.detail.description": "Populate Zone details.", "message.zone.detail.hint": "A Zone is the largest organizational unit in CloudStack, and it typically corresponds to a single datacenter. Zones provide physical isolation and redundancy. A zone consists of one or more Pods (each of which contains hosts and primary storage servers) and a secondary storage server which is shared by all pods in the zone.", diff --git a/ui/public/locales/ja_JP.json b/ui/public/locales/ja_JP.json index 850264fb3411..b7f32d7a2e12 100644 --- a/ui/public/locales/ja_JP.json +++ b/ui/public/locales/ja_JP.json @@ -2932,7 +2932,6 @@ "message.error.domain": "ドメインを入力し、ROOTドメインは空のままにします", "message.error.enable.saml": "SAML SSOを有効にするユーザーIDが見つかりません。手動で有効にしてください。", "message.error.endip": "終了IPを入力してください", - "message.error.fixed.offering.kvm": "固定コンピューティングオファリングでKVMハイパーバイザーを利用するVMをスケールアップすることはできません。", "message.error.gateway": "ゲートウェイに入ってください", "message.error.host.name": "ホスト名を入力してください", "message.error.host.password": "ホストパスワードを入力してください", diff --git a/ui/public/locales/pt_BR.json b/ui/public/locales/pt_BR.json index c7ca36c1278d..91f0fd895e58 100644 --- a/ui/public/locales/pt_BR.json +++ b/ui/public/locales/pt_BR.json @@ -1,27 +1,35 @@ { "alert.service.domainrouter": "Roteador do dom\u00ednio", +"error.dedicate.bgp.peer.failed": "Falha ao dedicar o par BGP", +"error.dedicate.ipv4.subnet.failed": "Falha ao dedicar a sub-rede IPv4", "error.dedicate.cluster.failed": "Falha ao dedicar cluster", "error.dedicate.host.failed": "Falha ao dedicar host", "error.dedicate.pod.failed": "Falha ao dedicar pod", "error.dedicate.zone.failed": "Falha ao dedicar zona", +"error.empty.counter.operator.threshold": "Contador, Operador ou Limite est\u00e1 vazio", "error.execute.api.failed": "Falha ao executar API", "error.fetching.async.job.result": "Foi encontrado um erro ao buscar o resultado do job ass\u00edncrono", -"error.form.message": "H\u00e1 problemas no formul\u00e1rio. Por favor, corrija-os.", +"error.form.message": "H\u00e1 problemas no formul\u00e1rio. Por favor, corrija-os", "error.password.not.match": "Os campos de senha n\u00e3o coincidem", +"error.release.dedicate.bgp.peer": "Falha ao liberar o par BGP dedicado", "error.release.dedicate.cluster": "Falha ao liberar cluster dedicado.", "error.release.dedicate.host": "Falha ao liberar host dedicado.", +"error.release.dedicate.ipv4.subnet": "Falha ao liberar a sub-rede IPv4 dedicada", "error.release.dedicate.pod": "Falha ao liberar pod dedicado.", "error.release.dedicate.zone": "Falha ao liberar zona dedicada.", +"error.unable.to.add.setting.extraconfig": "N\u00e3o \u00e9 permitido adicionar configura\u00e7\u00e3o para extraconfig. Por favor, habilite o par\u00e2metro extraconfig para a m\u00e1quina virtual", "error.unable.to.proceed": "N\u00e3o foi poss\u00edvel proceder. Por favor, contate um administrador.", "firewall.close": "Firewall", -"icmp.code.desc": "Informe -1 para permitir todos os c\u00f3digos ICMP.", -"icmp.type.desc": "Informe -1 para permitir todos os tipos ICMP.", +"icmp.code.desc": "Informe -1 para permitir todos os c\u00f3digos ICMP", +"icmp.type.desc": "Informe -1 para permitir todos os tipos ICMP", "inline": "Inline", "label.about": "Sobre", "label.about.app": "Sobre o CloudStack", "label.accept": "Aceitar", -"label.accept.project.invitation": "Aceitar convite de projeto.", +"label.accept.project.invitation": "Aceitar convite de projeto", "label.access": "Acesso", +"label.access.key": "Chave de acesso", +"label.access.kubernetes.nodes": "Acessar n\u00f3s Kubernetes", "label.accesskey": "Chave de acesso", "label.account": "Conta", "label.account.and.security.group": "Contas e grupos de seguran\u00e7a", @@ -45,19 +53,27 @@ "label.action.attach.iso": "Anexar ISO", "label.action.bulk.delete.egress.firewall.rules": "Apagar em massa as regras de sa\u00edda do firewall.", "label.action.bulk.delete.firewall.rules": "Apagar em massa as regras do firewall.", +"label.action.bulk.delete.ip.v6.firewall.rules": "Apagar em massa as regras de firewall IPv6.", "label.action.bulk.delete.isos": "Apagar em massa as ISOs.", "label.action.bulk.delete.load.balancer.rules": "Apagar em massa as regras de balanceamento de carga.", "label.action.bulk.delete.portforward.rules": "Apagar em massa as regras de encaminhamento de porta.", +"label.action.bulk.delete.routing.firewall.rules": "Remover em massa regras de firewall de Roteamento IPv4", +"label.action.bulk.delete.snapshots": "Excluir snapshots em massa", "label.action.bulk.delete.templates": "Apagar em massa as regras de template.", "label.action.bulk.release.public.ip.address": "Liberar em massa os enderec\u0327os de IPs P\u00fablicos.", "label.action.cancel.maintenance.mode": "Cancelar modo de manuten\u00e7\u00e3o", "label.action.change.password": "Trocar de senha", +"label.action.change.primary.storage.scope": "Alterar escopo do armazenamento prim\u00e1rio", +"label.action.clear.webhook.deliveries": "Limpar entregas do webhook", +"label.action.delete.webhook.deliveries": "Excluir entregas do webhook", "label.action.configure.stickiness": "Ader\u00eancia", "label.action.copy.iso": "Copiar ISO", +"label.action.copy.snapshot": "Copiar Snapshot", "label.action.copy.template": "Copiar template", "label.action.create.snapshot.from.vmsnapshot": "Criar snapshot a partir de uma snapshot de VM", "label.action.create.template.from.volume": "Criar template a partir do disco", "label.action.create.volume": "Criar disco", +"label.action.create.volume.add": "Criar e Adicionar Volume", "label.action.delete.account": "Remover conta", "label.action.delete.backup.offering": "Remover oferta de backup", "label.action.delete.cluster": "Remover cluster", @@ -65,20 +81,29 @@ "label.action.delete.domain": "Remover dom\u00ednio", "label.action.delete.egress.firewall": "Remover regra de sa\u00edda do firewall", "label.action.delete.firewall": "Remover regra de firewall", +"label.action.delete.guest.os": "Excluir Guest OS", +"label.action.delete.guest.os.hypervisor.mapping": "Excluir mapeamento de virtualizador do Guest OS", +"label.action.delete.interface.static.route": "Remover rota est\u00e1tica da interface Tungsten Fabric", "label.action.delete.ip.range": "Remover intervalo de IPs", "label.action.delete.iso": "Remover ISO", "label.action.delete.load.balancer": "Remover regra do balanceador de carga", "label.action.delete.network": "Remover rede", +"label.action.delete.network.permission": "Excluir permiss\u00e3o de Rede", +"label.action.delete.network.static.route": "Remover rota est\u00e1tica de Rede Tungsten Fabric", "label.action.delete.node": "Remover nodo", +"label.action.delete.oauth.provider": "Excluir provedor OAuth", +"message.action.delete.object.storage": "Por favor, confirme que voc\u00ea deseja excluir este Object Storage", "label.action.delete.physical.network": "Remover rede f\u00edsica", "label.action.delete.pod": "Remover pod", "label.action.delete.primary.storage": "Remover armazenamento prim\u00e1rio", +"label.action.delete.routing.firewall.rule": "Excluir regra de firewall de Roteamento IPv4", "label.action.delete.secondary.storage": "Remover armazenamento secund\u00e1rio", "label.action.delete.security.group": "Remover grupo de seguran\u00e7a", "label.action.delete.service.offering": "Remover plano", "label.action.delete.snapshot": "Remover snapshot", "label.action.delete.system.service.offering": "Remover oferta de servi\u00e7o de sistema", "label.action.delete.template": "Remover template", +"label.action.delete.tungsten.router.table": "Remover tabela de rotas Tungsten Fabric da Rede", "label.action.delete.user": "Remover usu\u00e1rio", "label.action.delete.volume": "Remover disco", "label.action.delete.zone": "Remover zona", @@ -87,11 +112,16 @@ "label.action.destroy.volume": "Destruindo volume", "label.action.detach.disk": "Desanexar disco", "label.action.detach.iso": "Desanexar ISO", +"label.action.disable.2FA.user.auth": "Desativar Autentica\u00e7\u00e3o de dois fatores do usu\u00e1rio", "label.action.disable.account": "Desativar conta", "label.action.disable.cluster": "Desativar cluster", +"label.action.disable.disk.offering": "Desativar oferta de disco", "label.action.disable.physical.network": "Desabilitar rede f\u00edsica", "label.action.disable.pod": "Desativar pod", +"label.action.disable.role": "Desativar Fun\u00e7\u00e3o", "label.action.disable.static.nat": "Desativar NAT est\u00e1tico", +"label.action.disable.service.offering": "Desativar oferta de servi\u00e7o", +"label.action.disable.system.service.offering": "Desativar oferta de servi\u00e7o do sistema", "label.action.disable.user": "Desativar usu\u00e1rio", "label.action.disable.zone": "Desativar zona", "label.action.download.iso": "Baixar ISO", @@ -102,22 +132,33 @@ "label.action.edit.domain": "Editar dom\u00ednio", "label.action.edit.instance": "Editar inst\u00e2ncia", "label.action.edit.iso": "Editar ISO", +"label.action.edit.nfs.options": "Editar op\u00e7\u00f5es de montagem NFS", +"label.action.edit.template": "Editar Template", "label.action.edit.zone": "Editar zona", "label.action.enable.account": "Ativar conta", "label.action.enable.cluster": "Ativar cluster", +"label.action.enable.disk.offering": "Ativar oferta de disco", "label.action.enable.maintenance.mode": "Ativar modo de manuten\u00e7\u00e3o", "label.action.enable.physical.network": "Habilitar rede f\u00edsica", "label.action.enable.pod": "Ativar pod", +"label.action.enable.role": "Ativar role", "label.action.enable.static.nat": "Ativar NAT est\u00e1tico", +"label.action.enable.service.offering": "Ativar oferta de servi\u00e7o", +"label.action.enable.system.service.offering": "Ativar oferta de servi\u00e7o do sistema", +"label.action.enable.two.factor.authentication": "Autentica\u00e7\u00e3o de dois fatores ativada", "label.action.enable.user": "Habilitar usu\u00e1rio", "label.action.enable.zone": "Ativar zona", "label.action.expunge.instance": "Eliminar inst\u00e2ncia", "label.action.force.reconnect": "Forc\u0327ar reconex\u00e3o", "label.action.generate.keys": "Gerar chaves", +"label.action.generate.api.secret.keys": "Gerar novos pares de chave de API", "label.action.get.diagnostics": "Obter diagn\u00f3stico", +"label.action.health.monitor": "Monitor de sa\u00fade", "label.action.image.store.read.only": "Colocar armazenamento secund\u00e1rio em modo somente leitura", "label.action.image.store.read.write": "Colocar armazenamento secund\u00e1rio em modo leitura-escrita", "label.action.import.export.instances": "Importar-exportar inst\u00e2ncias", +"label.action.import.unmanage.volumes": "Importar volumes de dados", +"label.action.ingest.instances": "Ingerir inst\u00e2ncias", "label.action.iso.permission": "Atualizar permiss\u00f5es da ISO", "label.action.iso.share": "Atualizar compartilhamento da ISO", "label.action.lock.account": "Bloquear conta", @@ -126,6 +167,9 @@ "label.action.migrate.router": "Migrar roteador", "label.action.migrate.systemvm": "Migrar VM de sistema", "label.action.migrate.systemvm.to.ps": "Migrar VM de sistema para outro armazenamento prim\u00e1rio", +"label.action.patch.systemvm": "Aplicar patch na VM de sistema", +"label.action.patch.systemvm.vpc": "Aplicar patch na VM de Sistema - Roteador Virtual de VPCs", +"label.action.patch.systemvm.processing": "Aplicando patch na VM de sistema....", "label.action.project.add.account": "Adicionar conta ao projeto", "label.action.project.add.user": "Adicionar usu\u00e1rio a um projeto", "label.action.quota.tariff.create": "Criar tarifa", @@ -136,21 +180,36 @@ "label.action.reboot.systemvm": "Reiniciar VM de sistema", "label.action.recover.volume": "Recuperar volume", "label.action.recurring.snapshot": "Snapshots recorrentes", +"label.action.resize.sharedfs": "Redimensionar Sistema de Arquivos Compartilhado", +"label.action.restart.sharedfs": "Reiniciar Sistema de Arquivos Compartilhado", "label.action.register.iso": "Registrar ISO", "label.action.register.template": "Registrar template a partir da URL", +"label.action.release.asnumber": "Liberar N\u00famero AS", "label.action.release.ip": "Liberar IP", +"label.action.release.reserved.ip": "Liberar IP reservado", "label.action.remove.host": "Remover host", +"label.action.remove.logical.router": "Remover Roteador L\u00f3gico", +"label.action.remove.network.policy": "Remover Pol\u00edtica de Rede", +"label.action.remove.nic": "Remover NIC", +"label.action.remove.router.table.from.interface": "Remover tabela de rotas Tungsten Fabric da interface", +"label.action.remove.tungsten.routing.policy": "Remover pol\u00edtica de roteamento Tungsten Fabric da Rede", +"label.action.reserve.ip": "Reservar IP P\u00fablico", +"label.action.reset.network.permissions": "Redefinir permiss\u00f5es de Rede", "label.action.reset.password": "Recuperar senha", "label.action.resize.volume": "Redimensionar volume", "label.action.revert.snapshot": "Reverter para snapshot", "label.action.router.health.checks": "Obter resultado das checagens de sa\u00fade", "label.action.run.diagnostics": "Executar diagn\u00f3sticos", "label.action.secure.host": "Propagar certificado de seguran\u00e7a para o host", +"label.action.set.as.source.nat.ip": "Tornar source NAT", +"label.action.setup.2FA.user.auth": "Configurar Autentica\u00e7\u00e3o de Dois Fatores do Usu\u00e1rio", "label.action.start.instance": "Iniciar inst\u00e2ncia", "label.action.start.router": "Iniciar roteador", "label.action.start.systemvm": "Iniciar VM de sistema", +"label.action.start.sharedfs": "Iniciar Sistema de Arquivos Compartilhado", "label.action.stop.instance": "Parar inst\u00e2ncia", "label.action.stop.router": "Parar roteador", +"label.action.stop.sharedfs": "Parar Sistema de Arquivos Compartilhado", "label.action.stop.systemvm": "Parar VM de sistema", "label.action.take.snapshot": "Criar snapshot", "label.action.template.permission": "Atualizar permiss\u00f5es do template", @@ -159,8 +218,15 @@ "label.action.unmanage.instance": "Parar de gerenciar inst\u00e2ncia", "label.action.unmanage.instances": "Parar de gerenciar inst\u00e2ncias", "label.action.unmanage.virtualmachine": "Parar de gerenciar VM", +"label.action.unmanage.volume": "Parar de gerenciar Volume", +"label.action.unmanage.volumes": "Parar de gerenciar Volumes", +"label.action.update.host": "Atualizar host", "label.action.update.offering.access": "Atualizar acesso \u00e0 oferta", +"label.action.update.security.groups": "Atualizar grupos de seguran\u00e7a", "label.action.update.resource.count": "Atualizar contagem de recursos", +"label.action.userdata.reset": "Redefinir Userdata", +"label.action.value": "A\u00e7\u00e3o/Valor", +"label.action.verify.two.factor.authentication": "Autentica\u00e7\u00e3o de dois fatores verificada", "label.action.vmsnapshot.create": "Criar snapshot de VM", "label.action.vmsnapshot.delete": "Remover snapshot de VM", "label.action.vmsnapshot.revert": "Reverter snapshot de VM", @@ -175,6 +241,7 @@ "label.add.acl": "Adiciona lista ACL", "label.add.affinity.group": "Adicionar um grupo de afinidade", "label.add.baremetal.dhcp.device": "Adicionar dispositivo DHCP baremetal", +"label.add.bgp.peer": "Adicionar par BGP", "label.add.bigswitchbcf.device": "Adicionar controlador BigSwitch BCF", "label.add.brocadevcs.device": "Adicionar switch Brocade Vcs", "label.add.by": "Adicionado por", @@ -182,40 +249,56 @@ "label.add.ciscoasa1000v": "Adicionar recurso CiscoASA1000v", "label.add.cluster": "Adicionar cluster", "label.add.compute.offering": "Adicionar oferta de computa\u00e7\u00e3o", +"label.add.condition": "Adicionar condi\u00e7\u00e3o", "label.add.disk.offering": "Adicionar oferta de disco", "label.add.domain": "Adicionar dom\u00ednio", "label.add.egress.rule": "Adicionar regra de sa\u00edda", "label.add.f5.device": "Adicionar dispositivo F5", "label.add.firewall": "Adicionar regra de firewall", +"label.add.firewallrule": "Adicionar Regra de Firewall", "label.add.guest.network": "Adicionar rede guest", +"label.add.guest.os": "Adicionar SO convidado", +"label.add.guest.os.hypervisor.mapping": "Adicionar mapeamento de hypervisor do Guest OS", +"label.add.gui.theme": "Adicionar tema da GUI", "label.add.host": "Adicionar host", "label.add.ingress.rule": "Adicionar regra de entrada", "label.add.intermediate.certificate": "Adicionar certificado intermedi\u00e1rio", "label.add.internal.lb": "Adicionar LB interno", "label.add.ip.range": "Adicionar intervalo de IPs", +"label.add.ip.v6.prefix": "Adicionar prefixo IPv6", +"label.add.ipv4.subnet": "Adicionar sub-rede IPv4 para redes Roteadas", "label.add.isolated.network": "Adiciona rede isolada", +"label.add.key.value": "Adicionar par de chaves valor", "label.add.kubernetes.cluster": "Adicionar cluster Kubernetes", "label.add.ldap.account": "Adicionar conta LDAP", "label.add.acl.name": "Nome da lista ACL", + "label.add.logical.router": "Adicionar Roteador L\u00f3gico a esta Rede", "label.add.more": "Adicionar mais", "label.add.netscaler.device": "Adicionar dispositivo Netscaler", "label.add.network": "Adicionar rede", "label.add.network.acl": "Adicione ACL de rede", "label.add.network.acl": "Adicionar lista de ACL de rede", "label.add.network.offering": "Adicionar oferta de rede", +"label.add.network.permission": "Adicionar permiss\u00e3o de Rede", "label.add.new.gateway": "Adicionar novo gateway", "label.add.new.tier": "Adicionar nova camada", "label.add.niciranvp.device": "Adicionar controlador Nvp", "label.add.note": "Adicionar coment\u00e1rio", +"label.add.object.storage" : "Adicionar Object Storage", "label.add.opendaylight.device": "Adiciona controlador OpenDaylight", "label.add.pa.device": "Adicionar dispositivo Palo Alto", +"label.add.param": "Adicionar par\u00c2metro", "label.add.physical.network": "Adicionar rede f\u00edsica", "label.add.pod": "Adicionar pod", +"label.add.policy": "Adicionar pol\u00edtica", +"label.add.prefix": "Adicionar prefixo", "label.add.primary.storage": "Adicionar armazenamento prim\u00e1rio", "label.add.private.gateway": "Adicionar gateway privado", "label.add.resources": "Adicionar recursos", "label.add.role": "Adicionar fun\u00e7\u00e3o", "label.add.route": "Adicionar rota", +"label.add.router.table.to.instance": "Adicionar tabela de roteamento a esta Inst\u00e2ncia", +"label.add.routing.policy": "Adicionar pol\u00edtica de roteamento", "label.add.rule": "Adicionar regra", "label.add.secondary.ip": "Adicionar IP secund\u00e1rio", "label.add.secondary.storage": "Adicionar armazenamento secund\u00e1rio", @@ -224,8 +307,26 @@ "label.add.srx.device": "Adicionar dispositivo SRX", "label.add.static.route": "Adicionar rota est\u00e1tica", "label.add.system.service.offering": "Adicionar oferta de servi\u00e7o para VMs de sistema", +"label.add.term.then": "Adicionar termo", "label.add.traffic": "Adicionar tr\u00e1fego", "label.add.traffic.type": "Adicionar tipo de tr\u00e1fego", +"label.add.tungsten.address.group": "Adicionar Grupo de Endere\u00e7os", +"label.add.tungsten.fabric.route": "Adicionar tabela de roteamento de Rede Tungsten Fabric", +"label.add.tungsten.firewall.policy": "Adicionar Pol\u00edtica de Firewall", +"label.add.tungsten.firewall.rule": "Adicionar Regra de Firewall", +"label.add.tungsten.interface.route": "Adicionar tabela de roteamento de interface Tungsten Fabric", +"label.add.tungsten.interface.static.route": "Adicionar rota est\u00e1tica de interface Tungsten Fabric", +"label.add.tungsten.logical.route": "Adicionar Roteador L\u00f3gico", +"label.add.tungsten.network.static.route": "Adicionar rota est\u00e1tica de Rede Tungsten Fabric", +"label.add.tungsten.policy": "Adicionar Pol\u00edtica", +"label.add.tungsten.policy.set": "Adicionar Conjunto de Pol\u00edticas de Aplica\u00e7\u00e3o", +"label.add.tungsten.router.table": "Adicionar tabela de roteamento a esta Rede", +"label.add.tungsten.routing.policy": "Adicionar pol\u00edtica de roteamento a esta Rede", +"label.add.tungsten.service.group": "Adicionar Grupo de Servi\u00e7os", +"label.add.tungsten.tag": "Adicionar Tag", +"label.add.tungsten.tag.type": "Adicionar Tipo de Tag", +"label.add.upstream.ipv4.routes": "Adicionar roteamento upstream IPv4", +"label.add.upstream.ipv6.routes": "Adicionar roteamento upstream IPv6", "label.add.user": "Adicionar usu\u00e1rio", "label.add.vm": "Adicionar VM", "label.add.vms": "Adicionar VMs", @@ -240,7 +341,9 @@ "label.adding": "Adicionando", "label.adding.user": "Adicionando usu\u00e1rio", "label.address": "Endere\u00e7o", +"label.address.group": "Grupo de endere\u00e7os", "label.admin": "Administrador", +"label.adminsonly": "Vis\u00edvel apenas para administradores", "label.advanced": "Avan\u00e7ado", "label.advanced.mode": "Modo avan\u00e7ado", "label.affinity": "Afinidade", @@ -248,6 +351,7 @@ "label.affinitygroup": "Grupo de afinidade", "label.agent.password": "Senha do agente", "label.agent.username": "Usu\u00e1rio do agente", +"label.agentcount": "N\u00famero de agentes conectados", "label.agentport": "Porta do agente", "label.agentstate": "Estado do agente", "label.agree": "Concordo", @@ -257,49 +361,85 @@ "label.algorithm": "Algoritmo", "label.all": "Todos", "label.all.available.data": "Todos os dados dispon\u00edveis", +"label.all.ipv6": "Todos IPv6", "label.all.zone": "Todas as zonas", "label.allocated": "Alocado", +"label.allocatedonly": "Alocado", "label.allocationstate": "Estado da aloca\u00e7\u00e3o", "label.allow": "Permitir", +"label.allow.duplicate.macaddresses": "Permitir endere\u00e7os MAC duplicados", "label.allowuserdrivenbackups": "Permitir backups manuais pelos usu\u00e1rios", "label.annotation": "Coment\u00e1rio", "label.annotations": "Coment\u00e1rios", "label.annotation.everyone": "Vis\u00edvel a todos", "label.anti.affinity": "Anti-afinidade", "label.anti.affinity.group": "Grupo de anti-afinidade", +"label.api.docs": "Documenta\u00e7\u00c3o da API", +"label.api.docs.description": "Para informa\u00e7\u00f5es sobre como as APIs funcionam e dicas de como us\u00e1-las, clique aqui para ver o Guia do Desenvolvedor", +"label.api.docs.count": "APIs dispon\u00edveis para sua conta", "label.api.version": "Vers\u00e3o da API", "label.apikey": "Chave da API", "label.app.cookie": "AppCookie", "label.app.name": "CloudStack", +"label.application.policy.set": "Conjunto de Pol\u00edticas de Aplica\u00e7\u00e3o", "label.apply": "Aplicar", +"label.apply.tungsten.firewall.policy": "Aplicar Pol\u00edtica de Firewall", +"label.apply.tungsten.network.policy": "Aplicar Pol\u00edtica de Rede", +"label.apply.tungsten.tag": "Aplicar tag", +"label.arch": "Arquitetura", +"label.archived": "Arquivado", "label.archive": "Arquivo", "label.archive.alerts": "Arquivar alertas", "label.archive.events": "Arquivar eventos", "label.as.default": "como padr\u00e3o", +"label.asnumber": "N\u00famero AS", +"label.asnumbers": "N\u00fameros AS", +"label.asnrange": "Faixa AS", "label.assign": "Atribuir", "label.assign.instance.another": "Atribuir inst\u00e2ncia para outra conta", "label.assign.vms": "Atribuir VMs", "label.assigning.vms": "Atribuindo VMs", +"label.associated.resource": "Recurso associado", "label.associatednetwork": "Rede associada", "label.associatednetworkid": "ID de rede associado", "label.associatednetworkname": "Nome da rede", "label.asyncbackup": "Backup ass\u00edncrono", +"label.attaching": "Anexando", +"label.authentication.method": "M\u00e9todo de Autentica\u00e7\u00e3o", +"label.authentication.sshkey": "Chave SSH do Sistema", +"label.autoscale": "AutoScale", +"label.autoscalevmgroupname": "Grupo de AutoScale", "label.author.email": "E-mail do autor", "label.author.name": "Nome do autor", +"label.auto.assign": "Atribuir automaticamente", "label.auto.assign.diskoffering.disk.size": "Atribuir automaticamente a oferta correspondente ao tamanho do disco", "label.auto.assign.random.ip": "Atribuir automaticamente um enderec\u0327o de IP aleat\u00f3rio", "label.auto.refresh.statistics": "Tempo entre atualiza\u00e7\u00f5es autom\u00e1ticas", "label.auto.refresh.statistics.none": "Nenhum", +"label.automigrate.volume": "Migrar volume automaticamente para outro pool de armazenamento, se neces\u00e1rio", +"label.autoscale.vm.groups": "Grupos de AutoScale", +"label.autoscale.vm.profile": "Perfil de Inst\u00e2ncia de AutoScale", "label.autoscalingenabled": "Escalonamento autom\u00e1tico", "label.availability": "Disponibilidade", "label.available": "Dispon\u00edvel", +"label.availableprocessors": "N\u00facleos de processador dispon\u00edveis", +"label.availablevirtualmachinecount": "Inst\u00e2ncias Dispon\u00edveis", "label.back": "Voltar", "label.backup": "Backup", "label.backups": "Backups", +"label.back.login": "Voltar à página de login", +"label.backup": "Backups", "label.backup.attach.restore": "Restaurar e anexar volume de backup", +"label.backuplimit": "Limite de backups", +"label.backup.storage": "Armazenamento de backup", +"label.backupstoragelimit": "Limite de armazenamento de backup (GiB)", +"label.backup.configure.schedule": "Configurar Agendamento de Backup", "label.backup.offering.assign": "Atribuir VM a oferta de backup", "label.backup.offering.remove": "Remover VM de oferta de backup", "label.backup.offerings": "Ofertas de backup", +"label.backup.repository": "Reposit\u00f3rio de Backup", +"label.backup.repository.add": "Adicionar reposit\u00f3rio de backup", +"label.backup.repository.remove": "Remover reposit\u00f3rio de backup", "label.backup.restore": "Restaurar backup da VM", "label.backupofferingid": "Oferta de backup", "label.backupofferingname": "Oferta de backup", @@ -316,6 +456,8 @@ "label.based.on.role.id.or.type": "Criar uma fun\u00e7\u00e3o baseado no ID da fun\u00e7\u00e3o ou no seu tipo", "label.basic": "B\u00e1sico", "label.bcfdeviceid": "ID", +"label.bgp.peers": "Pares BGP", +"label.bgp.peer.set.reservation.desc": "Voc\u00ea pode tornar o par BGP p\u00fablico, ou pode dedic\u00e1-lo/reserv\u00e1-lo para um Dom\u00ednio ou uma Conta", "label.bigswitch.controller.address": "Endere\u00e7o do controlador BigSwitch BCF", "label.bladeid": "ID da l\u00e2mina", "label.blades": "L\u00e2minas", @@ -327,7 +469,13 @@ "label.broadcastdomaintype": "Tipo de dom\u00ednio broadcast", "label.broadcasturi": "URI de broadcast", "label.brocade.vcs.address": "Endere\u00e7o do switch Vcs", +"label.browser": "Navegador", "label.bucket": "Bucket", +"label.bucket.delete": "Excluir Bucket", +"label.bucket.policy": "Pol\u00edtica do Bucket", +"label.bucket.update": "Atualizar Bucket", +"label.bucketlimit": "Limite de Bucket", +"label.buckets": "Buckets", "label.by.account": "por conta", "label.by.domain": "por dom\u00ednio", "label.by.level": "por n\u00edvel", @@ -338,6 +486,9 @@ "label.bypassvlanoverlapcheck": "Ignorar a sobreposi\u00e7\u00e3o de ID/intervalo de VLAN", "label.cachemode": "Tipo do cache de escrita", "label.cancel": "Cancelar", +"label.cancel.host.as.degraded": "Cancelar host como degradado", +"label.cancel.shutdown": "Cancelar Desligamento", +"label.cancelmaintenance": "Cancelar manuten\u00e7\u00e3o", "label.capacity": "Capacidade", "label.capacitybytes": "Capacidade de bytes", "label.capacityiops": "IOPS total", @@ -348,9 +499,13 @@ "label.certificate.upload.failed": "Falha ao carregar certificado", "label.certificate.upload.failed.description": "Falha ao atualizar certificado SSL. Falha na verifica\u00e7\u00e3o do certificado.", "label.certificateid": "ID do certificado", +"label.change": "Alterar", "label.change.affinity": "Mudar afinidade", +"label.change.bgp.peers": "Alterar pares BGP", +"label.change.disk.offering": "Alterar oferta de disco", "label.change.ip.address": "Trocar endere\u00e7o IP", "label.change.ipaddress": "Mudan\u00e7a de endere\u00e7o IP para NIC", +"label.change.offering.for.volume": "Alterar oferta de disco para o volume", "label.change.service.offering": "Alterar oferta de servi\u00e7o", "label.character": "Caracter", "label.checksum": "Checksum", @@ -359,6 +514,7 @@ "label.cidr": "CIDR", "label.cidr.destination.network": "CIDR da rede de destino", "label.cidrlist": "Lista de CIDRs", +"label.cidrsize": "Tamanho do CIDR", "label.cisco.nexus1000v.ip.address": "Endere\u00e7o IP do Nexus 1000v", "label.cisco.nexus1000v.password": "Senha do Nexus 1000v", "label.cisco.nexus1000v.username": "Usu\u00e1rio do Nexus 1000v", @@ -369,42 +525,75 @@ "label.cleanup": "Limpar", "label.clear": "Limpar", "label.clear.list": "Limpar lista", +"label.clear.notification": "Limpar notifica\u00e7\u00e3o", +"label.clientid": "ID do Cliente Provedor", "label.close": "Fechar", +"label.cloud.managed": "Gerenciado pela Nuvem", "label.cloudian.storage": "Armazenamento Cloudian", "label.cluster": "Cluster", "label.cluster.name": "Nome do cluster", "label.cluster.size": "Tamanho do cluster", "label.clusterid": "Cluster", "label.clustername": "Nome do cluster", +"label.clusternamelabel": "Nome do cluster", "label.clusters": "Clusters", "label.clustertype": "Tipo de cluster", "label.clvm": "CLVM", "label.code": "C\u00f3digo", +"label.collectiontime": "Tempo de coleta", +"label.columns": "Colunas", "label.comma.separated.list.description": "Insira uma lista de comandos separados por v\u00edrgulas", +"label.command": "Comando", "label.comments": "Coment\u00e1rios", +"label.commonnames": "Nomes Comuns (CN)", +"label.communities": "Comunidades", "label.community": "Comunidade", "label.complete": "Complete", +"label.compressionstatus": "Estado de compress\u00e3o", "label.compute": "Computa\u00e7\u00e3o", "label.compute.offerings": "Oferta de computa\u00e7\u00e3o", +"label.compute.offering.for.sharedfs.instance": "Oferta de computa\u00e7\u00e3o para Inst\u00e2ncia", +"label.computeonly.offering": "Oferta de disco apenas para computa\u00e7\u00e3o", +"label.computeonly.offering.tooltip": "Op\u00e7\u00e3o para especificar informa\u00e7\u00f5es relacionadas ao disco ROOT na oferta de computa\u00e7\u00e3o ou para vincular diretamente uma oferta de disco \u00e0 oferta de computa\u00e7\u00e3o", +"label.conditions": "Condi\u00e7\u00f5es", "label.configuration": "Configura\u00e7\u00e3o", "label.configure": "Configurar", +"label.configure.app": "Configurar o App", +"label.configure.instance": "Configurar inst\u00e2ncia", +"label.configure.health.monitor": "Configurar Monitor de Sa\u00fade", "label.configure.ldap": "Configurar LDAP", "label.configure.ovs": "Configure Ovs", "label.configure.sticky.policy": "Configurar sticky policy", "label.confirm.delete.egress.firewall.rules": "Por favor, confirme se deseja apagar as regras de firewall de sa\u00edda selecionadas", "label.confirm.delete.firewall.rules": "Por favor, confirme se deseja apagar as regras de firewall selecionadas", +"label.confirm.delete.ip.v6.firewall.rules": "Por favor, confirme que deseja excluir as regras de firewall IPv6 selecionadas", "label.confirm.delete.isos": "Por favor, confirme se deseja apagar as ISOs selecionadas", "label.confirm.delete.loadbalancer.rules": "Por favor, confirme se deseja apagar as regras de balanceamento de carga selecionadas", "label.confirm.delete.portforward.rules": "Por favor, confirme se deseja apagar as regras de encaminhamento de porta selecionadas", +"label.confirm.delete.routing.firewall.rules": "Por favor, confirme que deseja excluir as regras de firewall de Roteamento IPv4 selecionadas", +"label.confirm.delete.snapshot.zones": "Por favor, confirme que deseja excluir o Snapshot nas zonas selecionadas", "label.confirm.delete.templates": "Por favor, confirme se deseja apagar os templates selecionados", +"label.confirm.delete.tungsten.address.group": "Por favor, confirme que deseja excluir este Grupo de Endere\u00e7os", +"label.confirm.delete.tungsten.firewall.policy": "Por favor, confirme que deseja excluir esta Pol\u00edtica de Firewall", +"label.confirm.delete.tungsten.policy": "Por favor, confirme que deseja excluir esta Pol\u00edtica", +"label.confirm.delete.tungsten.policy.set": "Por favor, confirme que deseja excluir este Conjunto de Pol\u00edticas de Aplica\u00e7\u00e3o", +"label.confirm.delete.tungsten.service.group": "Por favor, confirme que deseja excluir este Grupo de Servi\u00e7os", +"label.confirm.delete.tungsten.tag": "Por favor, confirme que deseja excluir esta Tag", +"label.confirm.delete.tungsten.tag.type": "Por favor, confirme que deseja excluir este Tipo de Tag", +"label.confirm.remove.logical.router": "Por favor, confirme que deseja excluir este Roteador L\u00f3gico", "label.confirm.release.public.ip.addresses": "Por favor, confirme se deseja liberar os endere\u00e7os IPs selecionados", +"label.confirm.remove.network.route.table": "Por favor, confirme que deseja excluir esta Tabela de Rotas de Rede", +"label.confirm.remove.route.table": "Por favor, confirme que deseja excluir esta Tabela de Rotas de Interface", "label.confirmacceptinvitation": "Por favor, confirme se deseja participar deste projeto", "label.confirmation": "Confirma\u00e7\u00e3o", "label.confirmdeclineinvitation": "Voc\u00ea tem certeza que quer rejeitar este convite de projeto?", "label.confirmpassword": "Confirme a senha", "label.confirmpassword.description": "Por favor, digite a mesma senha novamente", +"label.connect": "Conectar", "label.connectiontimeout": "Tempo limite de conex\u00e3o", "label.conservemode": "Modo conservativo", +"label.considerlasthost": "Considerar \u00faltimo Host", +"label.consoleproxy": "Console proxy", "label.console.proxy": "Console proxy", "label.console.proxy.vm": "VM da console proxy", "label.continue": "Continuar", @@ -413,13 +602,19 @@ "label.copied.clipboard": "Copiado para a \u00c1rea de transfer\u00eancia", "label.copy": "Copiar", "label.copy.clipboard": "Copiar para \u00c1rea de transfer\u00eancia", +"label.copy.password": "Copiar senha", "label.copyid": "Copiar ID", +"label.core": "Core", +"label.core.zone.type": "Tipo de zona Core", +"label.counter": "Contador", +"label.counter.name": "Nome do contador para o qual a pol\u00edtica ser\u00e1 avaliada", "label.cpu": "CPU", "label.cpu.sockets": "Sockets da CPU", "label.cpu.usage.info": "Informa\u00e7\u00f5es sobre o uso de CPU", "label.cpuallocated": "CPU alocada por VMs", "label.cpuallocatedghz": "Alocado", "label.cpulimit": "Limite de CPU", +"label.cpuload": "% de CPU em uso", "label.cpumaxdeviation": "Desvio", "label.cpunumber": "N\u00facleos da CPU", "label.cpusockets": "O n\u00famero de sockets de CPU", @@ -429,13 +624,17 @@ "label.cpuused": "CPU utilizada", "label.cpuusedghz": "CPU utilizada", "label.create": "Criar", -"label.create.instance": "Criar nova instância", "label.create.account": "Criar conta", +"label.create.asnrange": "Criar faixa de número AS", "label.create.backup": "Iniciar backup", +"label.create.bucket": "Criar Bucket", +"label.create.instance": "Criar inst\u00e2ncia na nuvem", +"label.create.instance.from.backup": "Criar nova inst\u00e2ncia a partir de backup", "label.create.network": "Criar nova rede", "label.create.nfs.secondary.staging.storage": "Criar armazenamento secund\u00e1rio tempor\u00e1rio", "label.create.project": "Criar um projeto", "label.create.project.role": "Criar fun\u00e7\u00e3o para o projeto", +"label.create.routing.policy": "Criar Pol\u00edtica de Roteamento", "label.create.site.vpn.connection": "Criar conex\u00e3o VPN site-to-site", "label.create.site.vpn.gateway": "Criar gateway de VPN site-to-site", "label.create.snapshot.for.volume": "Criar snapshot para volume", @@ -447,11 +646,19 @@ "label.create.tier.name.description": "Um nome \u00fanico para a camada", "label.create.tier.netmask.description": "M\u00e1scara de rede da camada. Por exemplo: 255.255.255.0", "label.create.tier.networkofferingid.description": "A oferta de rede da camada", +"label.create.tungsten.routing.policy": "Criar pol\u00edtica de roteamento Tungsten-Fabric", +"label.create.sharedfs": "Criar Sistema de Arquivos Compartilhado", "label.create.user": "Criar usu\u00e1rio", +"label.create.vm": "Criar Inst\u00e2ncia", +"label.create.vm.and.stay": "Criar Inst\u00e2ncia e permanecer nesta p\u00e1gina", "label.create.vpn.connection": "Criar uma conex\u00e3o VPN", +"label.create.webhook": "Criar Webhook", "label.created": "Criado", +"label.creating": "Criando", "label.creating.iprange": "Criando intervalos de IP", "label.credit": "Cr\u00e9dito", +"label.cron": "Express\u00e3o Cron", +"label.cron.mode": "Modo Cron", "label.crosszones": "Inter zonas", "label.currency": "Moeda", "label.current": "Atual", @@ -469,14 +676,22 @@ "label.data.pool": "Data pool", "label.data.pool.description": "\u00c9 necess\u00e1rio informar um data pool ao utilizar um Ceph pool com erasure code", "label.date": "Data", +"label.datetime.filter.period": "De {startDate} at\u00e9 {endDate}", +"label.datetime.filter.starting": "Iniciando {startDate}", +"label.datetime.filter.up.to": "At\u00e9 {endDate}", "label.day": "Dia", "label.day.of.month": "Dia do m\u00eas", "label.day.of.week": "Dia da semana", +"label.db.usage.metrics": "Servidor de BD/Uso", +"label.dbislocal": "O banco de dados roda localmente", "label.dc.name": "Nome do DC", +"label.declare.host.as.degraded": "Declarar host como degradado", "label.decline.invitation": "Rejeitar convite", "label.dedicate": "Dedicado", +"label.dedicate.bgp.peer": "Dedicar par BGP", "label.dedicate.cluster": "Cluster dedicado", "label.dedicate.host": "Dedicar host", +"label.dedicate.ipv4.subnet": "Dedicar sub-rede IPv4", "label.dedicate.pod": "Pod dedicado", "label.dedicate.vlan.vni.range": "Intervalo de VLAN/VNI dedicado", "label.dedicate.zone": "Zona dedicada", @@ -484,6 +699,9 @@ "label.dedicated.vlan.vni.ranges": "Intervalo(s) de VLAN/VNI dedicados", "label.dedicatedresources": "Recursos dedicados", "label.default": "Padr\u00e3o", +"label.default.network.domain.isolated.network": "Dom\u00ednio de rede padr\u00e3o para redes Isoladas", +"label.default.network.guestcidraddress.isolated.network": "CIDR guest padr\u00e3o para Redes Isoladas", +"label.default.project": "Projeto padr\u00e3o", "label.default.use": "Uso padr\u00e3o", "label.default.view": "Visualiza\u00e7\u00e3o padr\u00e3o", "label.defaultnetwork": "Rede padr\u00e3o", @@ -491,12 +709,17 @@ "label.delete.acl": "Apagar lista ACL", "label.delete.affinity.group": "Apagar grupo de afinidade", "label.delete.alerts": "Remover alertas", +"label.delete.asnrange": "Excluir Faixa AS", +"label.delete.autoscale.vmgroup": "Excluir grupo de auto escalonamento horizontal", "label.delete.backup": "Apagar backup", +"label.delete.backup.schedule": "Remover agendamento de backup", +"label.delete.bgp.peer": "Excluir par BGP", "label.delete.bigswitchbcf": "Remover controlador BigSwitch BCF", "label.delete.brocadevcs": "Remover switch Brocade Vcs", "label.delete.certificate": "Apagar certificado", "label.delete.ciscoasa1000v": "Apagar CiscoASA1000v", "label.delete.ciscovnmc.resource": "Apagar recurso CiscoVNMC", +"label.delete.condition": "Excluir condi\u00e7\u00e3o", "label.delete.dedicated.vlan.range": "Apagar intervalo de VLAN/VNI dedicado", "label.delete.domain": "Apagar dom\u00ednio", "label.delete.events": "Remover eventos", @@ -505,6 +728,8 @@ "label.delete.icon": "Remover \u00edcone", "label.delete.instance.group": "Apagar grupo de inst\u00e2ncias", "label.delete.internal.lb": "Apagar LB interno", +"label.delete.ipv4.subnet": "Excluir sub-rede IPv4", +"label.delete.ip.v6.prefix": "Excluir prefixo IPv6", "label.delete.netscaler": "Remover NetScaler", "label.delete.niciranvp": "Remover controlador Nvp", "label.delete.opendaylight.device": "Apagar controladora OpenDaylight", @@ -518,28 +743,68 @@ "label.delete.snapshot.policy": "Apagar pol\u00edtica de snapshot", "label.delete.srx": "Remover SRX", "label.delete.sslcertificate": "Apagar certificado SSL", +"label.delete.tag": "Remover tag", +"label.delete.term": "Excluir termo", +"label.delete.traffic.type": "Excluir tipo de tr\u00e1fego", +"label.delete.tungsten.address.group": "Excluir Grupo de Endere\u00e7os", +"label.delete.tungsten.fabric.tag": "Excluir Tag", +"label.delete.tungsten.fabric.tag.type": "Excluir Tipo de Tag", +"label.delete.tungsten.firewall.policy": "Excluir Pol\u00edtica de Firewall", +"label.delete.tungsten.policy": "Excluir Pol\u00edtica", +"label.delete.tungsten.policy.set": "Excluir Conjunto de Pol\u00edticas", +"label.delete.tungsten.service.group": "Excluir Grupo de Servi\u00e7os", "label.delete.volumes": "Volumes a serem apagados", "label.delete.vpn.connection": "Remover conex\u00e3o VPN", "label.delete.vpn.customer.gateway": "Remover gateway de VPN de usu\u00e1rio", "label.delete.vpn.gateway": "Remover gateway de VPN", "label.delete.vpn.user": "Apagar usu\u00e1rio VPN", +"label.delete.webhook": "Excluir Webhook", +"label.delete.webhook.delivery": "Excluir Entrega de Webhook", "label.deleteconfirm": "Por favor, confirme que voc\u00ea deseja apagar isto", "label.deleting": "Removendo", "label.deleting.failed": "Falha ao remover", "label.deleting.iso": "Removendo ISO", "label.deleting.template": "Remover template", +"label.deleting.snapshot": "Excluindo Snapshot", +"label.deleteprotection": "Prote\u00e7\u00e3o contra exclus\u00e3o", "label.demote.project.owner": "Rebaixar conta para fun\u00e7\u00e3o regular", "label.demote.project.owner.user": "Rebaixar usu\u00e1rio para fun\u00e7\u00e3o regular", "label.deny": "Negar", "label.deployasis": "Ler configura\u00e7\u00f5es da VM do OVA", "label.deploymentplanner": "Planejador de implanta\u00e7\u00e3o", +"label.desc.db.stats": "Estat\u00edsticas do Banco de Dados", +"label.desc.import.ext.kvm.wizard": "Importar Inst\u00e2ncia de host KVM remoto", +"label.desc.import.local.kvm.wizard": "Importar imagem QCOW2 do Armazenamento Local", +"label.desc.import.shared.kvm.wizard": "Importar imagem QCOW2 do Armazenamento Compartilhado", +"label.desc.import.unmanage.volume": "Importar e parar de gerenciar volume em Pools de Armazenamento", "label.desc.importexportinstancewizard": "Importar e exportar inst\u00e2ncias para/de um cluster VMWare existente", +"label.desc.importmigratefromvmwarewizard": "Importar inst\u00e2ncias do VMware para um cluster KVM", +"label.desc.ingesttinstancewizard": "Ingerir inst\u00e2ncias de um host KVM externo", +"label.desc.usage.stats": "Estat\u00edsticas do Usage Server", "label.description": "Descri\u00e7\u00e3o", +"label.destaddressgroupuuid": "Grupo de Endere\u00e7os de Destino", "label.destcidr": "CIDR de destino", +"label.destendport": "Porta Final de Destino", +"label.desthost": "Host de destino", "label.destination": "Destino", +"label.destination.cluster": "Cluster de Destino", +"label.destination.hypervisor": "Hypervisor de Destino", +"label.destination.pod": "Pod de Destino", +"label.destination.zone": "Zona de Destino", "label.destinationphysicalnetworkid": "ID de destino da rede f\u00edsica", +"label.destinationtype": "Tipo de Destino", +"label.destipprefix": "Endere\u00e7o de Rede de Destino", +"label.destipprefixlen": "Comprimento do Prefixo de Destino", +"label.destnetwork": "Rede de Destino", +"label.destnetworkuuid": "Rede", +"label.destport": "Portas de Destino", "label.destroy": "Apagar", "label.destroy.router": "Destruir roteador", +"label.destroy.sharedfs": "Destruir Sistema de Arquivos Compartilhado", +"label.destroying": "Destruindo", +"label.destroyed": "Destru\u00eddo", +"label.deststartport": "Porta Inicial de Destino", +"label.desttaguuid": "Tag de Destino", "label.details": "Detalhes", "label.deviceid": "ID do dispositivo", "label.devices": "Dispositivos", @@ -547,16 +812,21 @@ "label.direct.attached.public.ip": "IP p\u00fablico conectado diretamente", "label.direct.ips": "IPs diretos", "label.directdownload": "Download direto", +"label.direction": "Dire\u00e7\u00e3o", +"label.disable.autoscale.vmgroup": "Desativar grupo de auto escalonamento horizontal", "label.disable.host": "Desabilitar host", "label.disable.network.offering": "Desabilitar oferta de rede", "label.disable.provider": "Desabilitar provedor", "label.disable.storage": "Desabilitar pool de armazenamento", "label.disable.vpc.offering": "Desabilitar oferta VPC", "label.disable.vpn": "Desabilitar VPN", +"label.disable.webhook": "Desativar Webhook", "label.disabled": "Desativado", "label.disconnected": "\u00daltima desconex\u00e3o", "label.disk": "Disco", "label.disk.offerings": "Ofertas de disco", +"label.disk.path": "Caminho do Disco", +"label.disk.tooltip": "Nome do arquivo de Imagem de Disco no Pool de Armazenamento selecionado", "label.disk.selection": "Sele\u00e7\u00e3o de disco", "label.disk.size": "Tamanho do disco", "label.disk.usage.info": "Informa\u00e7\u00f5es sobre o uso de disco", @@ -581,6 +851,7 @@ "label.disksize": "Tamanho (em GB)", "label.disksizeallocated": "Disco alocado", "label.disksizeallocatedgb": "Alocado", +"label.disksizefree": "Disco livre", "label.disksizetotal": "Disco total", "label.disksizetotalgb": "Total", "label.disksizeunallocatedgb": "N\u00e3o alocado", @@ -602,27 +873,46 @@ "label.domainid": "ID do dom\u00ednio", "label.domainname": "Dom\u00ednio", "label.domainpath": "Dom\u00ednio", +"label.domainrouter": "Roteador Virtual", "label.domains": "Dom\u00ednios", "label.done": "Pronto", +"label.down": "Baixo", "label.download": "Download", +"label.download.csv": "Baixar CSV", "label.download.kubeconfig.cluster": "Baixar kubeconfig para o cluster

A ferramenta de linha de comando kubectl usa os arquivos kubeconfig para encontrar informa\u00e7\u00f5es necess\u00e1rias para escolher o cluster e se comunicar com o servidor da API do cluster.", "label.download.kubectl": "Baixar a ferramenta kubectl para a vers\u00e3o Kubernetes do cluster", "label.download.kubernetes.cluster.config": "Baixar a configura\u00e7\u00e3o do cluster Kubernetes", "label.download.percent": "Porcentagem do download", +"label.download.setting": "Baixar configura\u00e7\u00e3o", "label.download.state": "Estado do download", "label.dpd": "Detec\u00e7\u00e3o de correspondente morto", "label.driver": "Driver", +"label.drs": "DRS", +"label.drsimbalance": "Desequil\u00edbrio DRS", +"label.drs.generate.plan": "Gerar plano DRS", +"label.drs.no.plan.generated": "Nenhum plano DRS foi gerado pois o cluster n\u00e3o est\u00e1 desequilibrado", +"label.drs.plan": "Plano DRS", +"label.duration": "Dura\u00e7\u00e3o (em segundos)", "label.duration.custom": "Personalizado", "label.duration.1hour": "1 hora", "label.duration.6hours": "6 horas", "label.duration.12hours": "12 horas", "label.duration.24hours": "24 horas", "label.duration.7days": "7 dias", +"label.dynamic": "Din\u00e2mico", "label.dynamicscalingenabled": "Escalonamento din\u00e2mico habilitado", "label.dynamicscalingenabled.tooltip": "VM s\u00f3 pode ser dinamicamente escalonada quando o escalonamento din\u00e2mico estiver habilitado no template, oferta de computa\u00e7\u00e3o e nas configura\u00e7\u00e3oes globais", +"label.diskofferingstrictness": "Rigor da oferta de disco", +"label.disksizestrictness": "Rigor do tamanho do disco", +"label.edge": "Borda", +"label.edge.zone": "Zona de Borda", +"label.enable.autoscale.vmgroup": "Ativar Grupo de AutoScaleAutoScaleAutoScale", "label.edit": "Editar", "label.edit.acl": "Editar lista ACL", +"label.edit.account": "Editar conta", +"label.edit.acl.list": "Editar lista ACL", "label.edit.acl.rule": "Editar regra ACL", +"label.edit.autoscale.vmprofile": "Editar Perfil de Inst\u00e2ncia de AutoScale", "label.edit.nic": "Editar NIC", "label.edit.project.details": "Editar detalhes do projeto", "label.edit.project.role": "Editar fun\u00e7\u00e3o do projeto", @@ -640,11 +930,18 @@ "label.email": "Email", "label.enable.host": "Habilita host", "label.enable.network.offering": "Habilita oferta de rede", +"label.enable.oauth": "Ativar Login OAuth", "label.enable.provider": "Habilitar provedor", "label.enable.storage": "Habilitar pool de armazenamento", "label.enable.vpc.offering": "Habilitar oferta VPC", "label.enable.vpn": "Habilitar VPN", +"label.enable.webhook": "Ativar Webhook", +"label.enabled": "Ativado", +"label.encrypt": "Criptografar", +"label.encryption": "Criptografia", +"label.encryptroot": "Criptografar Disco ROOT", "label.end": "Fim", +"label.endasn": "Fim Como N\u00famero", "label.end.date": "Data de término", "label.end.date.and.time": "Data e hor\u00e1rio final", "label.end.ip": "IP final", @@ -656,7 +953,11 @@ "label.endipv6": "IP final IPv6", "label.endpoint": "Ponto de acesso", "label.endport": "Porta final", +"label.enter.code": "Insira o c\u00f3digo 2FA para verificar", +"label.enter.static.pin": "Insira o PIN est\u00e1tico para verificar", "label.enter.token": "Digite o token", +"label.entityid": "Entidade", +"label.entitytype": "Tipo de Entidade", "label.error": "Erro", "label.error.caught": "Erro detectado", "label.error.code": "C\u00f3digo de erro", @@ -666,10 +967,14 @@ "label.error.setting": "Erro de configura\u00e7\u00e3o", "label.error.something.went.wrong.please.correct.the.following": "Alguma coisa est\u00e1 errada; por favor corrija abaixo", "label.error.upper": "ERRO", +"label.errorinmaintenance": "Erro na manuten\u00e7\u00e3o", "label.espencryption": "Encripta\u00e7\u00e3o ESP", "label.esphash": "Hash ESP", "label.esplifetime": "Tempo de vida do ESP (segundos)", "label.esppolicy": "Pol\u00edtica ESP", +"label.estimatedcost": "Custo estimado", +"label.eventid": "Evento", +"label.eventtype": "Tipo de evento", "label.esx.host": "ESX/ESXi host", "label.event": "Eventos", "label.event.archived": "Evento arquivado", @@ -679,10 +984,21 @@ "label.every": "Cada", "label.example": "Exemplo", "label.example.plugin": "PluginDeExemplo", +"label.execute": "Executar", +"label.existing": "Existente", "label.expunge": "Eliminar", +"label.export.data.csv": "Exportar dados como CSV", +"label.export.details.csv": "Exportar detalhes como CSV", +"label.export.resources.csv": "Exportar recursos como CSV", +"label.export.rules": "Exportar Regras", +"label.expunge.sharedfs": "Expurgar Sistema de Arquivos Compartilhado", +"label.expungevmgraceperiod": "Per\u00edodo de car\u00eancia para expurgar Inst\u00e2ncia (em seg)", "label.expunged": "Eliminado", "label.expunging": "Eliminando", +"label.ext.hostname.tooltip": "Nome do Host Externo ou Endere\u00e7o IP", "label.external.link": "Link externo", +"label.external": "Externo", +"label.external.managed": "Gerenciado Externamente", "label.externalid": "ID externo", "label.externalloadbalanceripaddress": "Endere\u00e7o externo do balanceador de carga", "label.extra": "Argumentos extras", @@ -690,25 +1006,43 @@ "label.f5.ip.loadbalancer": "Balanceador de carga F5 Big Ip", "label.failed": "Falhou", "label.featured": "Em destaque", +"label.fetch.from.backup": "Buscar do backup", +"label.fetch.instances": "Buscar Inst\u00e2ncias", "label.fetch.latest": "Atualizar", +"label.fetched": "Buscado", +"label.filename": "Nome do Arquivo", "label.files": "Alterar arquivos a serem recuperados", +"label.filesystem": "Sistema de Arquivos", "label.filter": "Filtro", "label.filter.annotations.self": "Criado por mim", "label.filter.annotations.all": "Todos os coment\u00e1rios", "label.filterby": "Filtrar por", "label.fingerprint": "Impress\u00e3o digital", +"label.finish": "Finalizar", "label.firewall": "Firewall", +"label.firewall.policy": "Pol\u00edtica de Firewall", +"label.firewallpolicy": "Pol\u00edtica de Firewall", "label.firewallrule": "Regra de firewall", +"label.firewallruleuuid": "Regra de Firewall", "label.firstname": "Primeiro nome", +"label.firstname.lower": "primeiro nome", "label.fix.errors": "Corrija os erros", "label.fixed": "Fixa", "label.for": "para", "label.forbidden": "Proibido", +"label.force.ms.to.import.vm.files": "For\u00e7ar MS a importar arquivo(s) da VM para armazenamento tempor\u00e1rio", +"label.force.reboot": "For\u00e7ar reinicializa\u00e7\u00e3o", +"label.force.stop": "For\u00e7ar parada", "label.forced": "For\u00e7ar", "label.forceencap": "For\u00e7ar encapsulamento UDP de pacotes ESP", "label.forgedtransmits": "Transmiss\u00f5es forjadas", +"label.forgot.password": "Esqueceu a senha?", +"label.fornsx": "NSX", +"label.forvpc": "VPC", +"label.fsprovidername": "Provedor de Sistema de Arquivos Compartilhado", "label.format": "Formato", "label.free": "Livre", +"label.french.azerty.keyboard": "Teclado franc\u00eas AZERTY", "label.friday": "Sexta-feira", "label.from": "de", "label.from.lb": "de LB", @@ -717,6 +1051,7 @@ "label.fwdeviceid": "ID", "label.fwdevicestate": "Estado", "label.gateway": "Gateway", +"label.global": "Global", "label.global.settings": "Configura\u00e7\u00f5es globais", "label.globo.dns": "GloboDNS", "label.globo.dns.configuration": "Configurar GloboDNS", @@ -741,8 +1076,11 @@ "label.guest.ip.range": "Intervalo da rede guest", "label.guest.netmask": "M\u00e1scara da rede guest", "label.guest.networks": "Redes guest", +"label.guest.os": "SO Convidado", +"label.guest.os.hypervisor.mappings": "Mapeamentos de SO convidado", "label.guest.start.ip": "IP de in\u00edcio do guest", "label.guest.traffic": "Tr\u00e1fego da redes guest", +"label.guest.vlan": "VLAN Guest", "label.guestcidraddress": "CIDR da rede guest", "label.guestendip": "IP do fim do guest", "label.guestgateway": "Gateway da rede guest", @@ -762,11 +1100,15 @@ "label.haenable": "HA ativado", "label.haprovider": "Provedor HA", "label.hardware": "Hardware", +"label.hasrules":"Regras de FW definidas", "label.hastate": "Estado do HA", "label.header.backup.schedule": "Voc\u00ea pode definir cronogramas de backup recorrentes selecionando dentre as op\u00e7\u00f5es dispon\u00edveis abaixo e aplicando suas pol\u00edticas preferenciais", "label.header.volume.snapshot": "Voc\u00ea pode configurar snapshots recorrentes selecionando as op\u00e7\u00f5es dispon\u00edveis abaixo e aplicando suas pol\u00edticas preferenciais", "label.header.volume.take.snapshot": "Por favor confirme que voc\u00ea deseja criar uma snapshot para este volume.", +"label.headers": "Cabe\u00e7alhos", "label.health.check": "Checagem de sa\u00fade", +"label.heapmemoryused": "Mem\u00f3ria heap usada", +"label.heapmemorytotal": "Mem\u00f3ria heap dispon\u00edvel", "label.help": "Ajuda", "label.hideipaddressusage": "Ocultar uso do endere\u00e7o IP", "label.home": "In\u00edcio", @@ -774,11 +1116,19 @@ "label.host.alerts": "Hosts em estado de alerta", "label.host.name": "Nome do host", "label.host.tag": "Tag de host", +"label.hostcontrolstate": "Status do Recurso de Computa\u00e7\u00e3o", "label.hostid": "Host", "label.hostname": "Host", +"label.hostname.tooltip": "Host de Destino. O volume deve estar localizado no armazenamento local deste Host", "label.hostnamelabel": "Nome do host", "label.hosts": "Hosts", "label.hosttags": "Tags de host", +"label.hosttags.explicit": "Tags de Host definidas pela API", +"label.hosttags.explicit.abbr": "definido pela api", +"label.hosttags.explicit.description": "As tags de host definidas pelas APIs do CloudStack", +"label.hosttags.implicit": "Tags de Host definidas pelo Agente", +"label.hosttags.implicit.abbr": "definido pelo agente", +"label.hosttags.implicit.description": "As tags de host definidas pelo Agente CloudStack", "label.hourly": "A cada hora", "label.hypervisor": "Virtualizador", "label.hypervisor.capabilities": "Recursos do virtualizador", @@ -803,22 +1153,31 @@ "label.ikepolicy": "Pol\u00edtica IKE", "label.ikeversion": "Vers\u00e3o do IKE", "label.images": "Imagens", +"label.imagestoreid": "Armazenamento Secun\u00e1rio", "label.import.backup.offering": "Importar oferta de backup", "label.import.instance": "Importar inst\u00e2ncia", "label.import.offering": "Importar oferta", "label.import.role": "Importar fun\u00e7\u00e3o", +"label.import.volume": "Importar Volume", +"label.inactive": "Inativo", "label.in.progress": "em progresso", "label.in.progress.for": "em progresso para", "label.info": "Info", "label.info.upper": "INFO", "label.infrastructure": "Infraestrutura", +"label.ingest.instance": "Ingerir Inst\u00e2ncia", "label.ingress": "Entrada", "label.ingress.rule": "Regra de entrada", +"label.initial": "Inicial", +"label.initialized": "Inicializado", +"label.interface.route.table": "Tabela de Rotas de Interface", +"label.interface.router.table": "Tabela de Rotas de Interface", "label.insideportprofile": "Perfil de porta interna", "label.installwizard.addzoneintro.title": "Vamos adicionar uma zona", "label.installwizard.subtitle": "Este tour vai auxiliar voc\u00ea na configura\u00e7\u00e3o da sua instala\u00e7\u00e3o do CloudStack™", "label.installwizard.title": "Ol\u00e1, seja bem vindo ao CloudStack™", "label.instance": "Inst\u00e2ncia", +"label.instance.conversion.support": "Convers\u00e3o de Inst\u00e2ncia Suportada", "label.instance.groups": "Grupos de inst\u00e2ncia", "label.instance.name": "Nome da inst\u00e2ncia", "label.instancename": "Nome interno", @@ -834,35 +1193,61 @@ "label.internallb.name.description": "Nome \u00fanico para o LB interno", "label.internallb.sourceip.description": "Descri\u00e7\u00e3o do LB interno", "label.internallbvm": "LbVm interno", +"label.internetprotocol": "Protocolo de internet", +"label.invalid.number": "N\u00famero inv\u00e1lido", +"label.iodriverpolicy" : "Pol\u00edtica de driver de IO", +"label.iodriverpolicy.tooltip" : "A pol\u00edtica de driver de IO pode ser nativa, io_uring ou threads. Escolher a pol\u00edtica de IO para uma Inst\u00e2ncia substituir\u00e1 a op\u00e7\u00e3o do pool de armazenamento 'kvm.storage.pool.io.policy' se definida (apenas se iothreads estiver ativado)", +"label.iops": "IOPS", +"label.iothreadsenabled" : "IOThreads", +"label.iothreadsenabled.tooltip" : "Ativar aloca\u00e7\u00e3o de iothreads para hypervisor KVM", "label.interval": "Intervalo de verifica\u00e7\u00e3o (em seg)", "label.intervaltype": "Tipo de intervalo", "label.introduction.to.cloudstack": "Introdu\u00e7\u00e3o ao CloudStack™", "label.invitations": "Convites", "label.invite": "Convidar", "label.ip": "IP", +"label.ip.addresses": "Endere\u00e7os IP", +"label.ip.range.type": "Tipo de intervalo de IP", "label.ip.range": "Intervalo de IP", "label.ip.ranges": "Intervalos de IP", +"label.ip.v4": "IPv4", +"label.ip.v4.v6": "IPv4 + IPv6 (Pilha Dupla)", +"label.ip.v6": "IPv6", +"label.ip.v6.firewall": "Firewall IPv6", "label.ip4gateway": "Gateway IPV4", "label.ip4netmask": "M\u00e1scara de rede IPv4", +"label.ip4routes": "Rotas IPv6", +"label.ip4routing": "Roteamento IPv4", "label.ip6address": "Endere\u00e7o IPv6", "label.ip6cidr": "CIDR IPv6", "label.ip6dns1": "IPv6 DNS1", "label.ip6dns2": "IPv6 DNS2", "label.ip6gateway": "Gateway IPv6", +"label.ip6firewall": "Firewall IPv6", +"label.ip6routes": "Rotas IPv6", +"label.ip6routing": "Roteamento IPv6", +"label.ipv6.subnets": "Sub-redes IPv6", "label.ipaddress": "Endere\u00e7o IP", "label.iplimit": "Limites de IP p\u00fablico", +"label.ipprefix": "Prefixo IP", +"label.ipprefixlen": "Comprimento do Prefixo IP", "label.ips": "IPs", "label.ipsecpsk": "Chave IPSec pr\u00e9 compartilhada", "label.iptotal": "Total de endere\u00e7os IPs", "label.ipv4.cidr": "CIDR IPv4", "label.ipv4.dns1": "IPv4 DNS1", "label.ipv4.dns2": "IPv4 DNS2", +"label.ipv4.subnet.set.reservation.desc": "(opcional) Por favor, especifique um dom\u00ednio ou uma Conta para ser associada a esta sub-rede IPv4", +"label.ipv4.subnets": "Sub-redes IPv4", "label.ipv6.dns1": "IPv6 DNS1", "label.ipv6.dns2": "IPv6 DNS2", "label.iqn": "Alvo IQN", +"label.is.base64.encoded": "Codificado em Base64", "label.is.in.progress": "est\u00e1 em progresso", "label.is.shared": "\u00c9 compartilhado", +"label.is2faenabled": "2FA est\u00e1 ativado", "label.isadvanced": "Mostra ajustes avan\u00e7ados", +"label.isallocated": "Alocado", "label.iscsi": "iSCSI", "label.iscustomized": "Tamanho customizado", "label.iscustomizeddiskiops": "IOPS personalizado", @@ -870,6 +1255,8 @@ "label.isdedicated": "Dedicado", "label.isdefault": "\u00c9\u0089 padr\u00e3o", "label.isdynamicallyscalable": "Dinamicamente escal\u00e1vel", +"label.isencrypted": "Criptografado", +"label.isuserdefined": "Definido pelo us\u00e1rio", "label.istagarule": "Tag como regra JS", "label.isextractable": "Extra\u00edvel", "label.isfeatured": "Em destaque", @@ -903,13 +1290,20 @@ "label.items": "itens", "label.items.selected": "item(ns) selecionados", "label.japanese.keyboard": "Teclado japon\u00eas", +"label.javadistribution": "Distribui\u00e7\u00e3o Java Runtime", +"label.javaversion": "Vers\u00e3o Java Runtime", +"label.jsonconfiguration": "Configura\u00e7\u00e3o JSON", "label.keep": "Manter", +"label.kernelversion": "Vers\u00e3o do Kernel", +"label.keep.mac.address.on.public.nic": "Utilizar o mesmo endere\u00e7o MAC para a NIC p\u00fablica dos VRs", "label.key": "Chave", "label.keyboard": "Linguagem do teclado", "label.keyboardtype": "Tipo de teclado", "label.keypair": "Par de chaves SSH", +"label.keypairs": "Par(es) de chaves SSH", "label.kubeconfig.cluster": "Configura\u00e7\u00e3o do cluster Kubernetes", "label.kubernetes": "Kubernetes", +"label.kubernetes.access.details": "Os n\u00f3s Kubernetes podem ser acessados via SSH usando:
ssh -i [ssh_key] -p [port_number] cloud@[public_ip_address]

onde,
ssh_key: aponta para o arquivo de chave privada SSH correspondente \u00e0 chave que foi associada durante a cria\u00e7\u00e3o do cluster Kubernetes. Se nenhuma chave SSH foi fornecida durante a cria\u00e7\u00e3o do cluster, use a chave privada SSH do Management Server.
port_number: pode ser obtido na Aba de Encaminhamento de Porta (coluna Porta P\u00fablica)", "label.kubernetes.cluster": "Cluster Kubernetes", "label.kubernetes.cluster.create": "Criar cluster Kubernetes", "label.kubernetes.cluster.delete": "Remover cluster Kubernetes", @@ -918,6 +1312,8 @@ "label.kubernetes.cluster.stop": "Parar cluster Kubernetes", "label.kubernetes.cluster.upgrade": "Atualizar cluster Kubernetes", "label.kubernetes.dashboard": "Dashboard da UI do Kubernetes", +"label.kubernetes.dashboard.create.token": "Criar token para dashboard Kubernetes", +"label.kubernetes.dashboard.create.token.desc": "Desde o Kubernetes v1.24.0, n\u00e3o h\u00e1 gera\u00e7\u00e3o autom\u00e1tica de token de conta de servi\u00e7o baseado em segredo por motivo de seguran\u00e7a. Voc\u00ea precisa criar uma conta de servi\u00e7o e um Token Bearer opcional de longa dura\u00e7\u00e3o para a conta de servi\u00e7o", "label.kubernetes.isos": "ISOs Kubernetes", "label.kubernetes.service": "Servi\u00e7o Kubernetes", "label.kubernetes.version.add": "Adicionar vers\u00e3o do Kubernetes", @@ -925,6 +1321,7 @@ "label.kubernetes.version.update": "Gerenciar vers\u00e3o do Kubernetes", "label.kubernetesversionid": "Vers\u00e3o do Kubernetes", "label.kubernetesversionname": "Vers\u00e3o do Kubernetes", +"label.kvm": "KVM", "label.kvmnetworklabel": "Etiqueta de tr\u00e1fego KVM", "label.l2": "L2", "label.l2gatewayserviceuuid": "UUID do servi\u00e7o de gateway L2", @@ -932,11 +1329,18 @@ "label.label": "Etiqueta", "label.last.updated": "\u00daltima atualiza\u00e7\u00e3o", "label.lastannotated": "Data da \u00faltima anota\u00e7\u00e3o", +"label.lastboottime": "Hora de inicializa\u00e7\u00e3o da m\u00e1quina do Management Server", +"label.lastheartbeat": "\u00daltimo heartbeat", "label.lastname": "Sobrenome", "label.lastname.lower": "sobrenome", +"label.lastserverstart": "\u00daltima vez que o Management Server foi inicializado", +"label.lastserverstop": "Hora da \u00faltima parada para este Management Server", +"label.lastsuccessfuljob": "\u00daltimo job bem-sucedido", "label.launch": "Executar", "label.launch.vm": "Executar VM", "label.launch.vm.and.stay": "Iniciar VM e permanecer na p\u00e1gina", +"label.launch.vnf.appliance": "Lan\u00e7ar appliance VNF", +"label.launch.vnf.appliance.and.stay": "Lan\u00e7ar appliance VNF & permanecer nesta p\u00e1gina", "label.launch.zone": "Executar zona.", "label.lb.algorithm.leastconn": "Conex\u00f5es m\u00ednimas", "label.lb.algorithm.roundrobin": "Round-robin", @@ -947,6 +1351,8 @@ "label.lbdevicededicated": "Dedicado", "label.lbdeviceid": "ID", "label.lbdevicestate": "Estado", +"label.lbprovider": "Provedor de balanceador de carga", +"label.lbruleid": "ID do Balanceador de carga", "label.lbtype": "Tipo de balanceamento de carga", "label.ldap.configuration": "Configura\u00e7\u00e3o do LDAP", "label.ldap.group.name": "Grupo LDAP", @@ -955,6 +1361,8 @@ "label.limit": "Limitar", "label.limitcpuuse": "Limite da CPU", "label.limits": "Configurar limites", +"label.limits.configure": "Configurar limites", +"label.link": "Link", "label.link.domain.to.ldap": "Link dom\u00ednio para LDAP", "label.linklocalip": "Endere\u00e7o IP do Control", "label.linux": "Linux", @@ -963,6 +1371,8 @@ "label.list.nodes": "Listar nodos", "label.list.pods": "Listar pods", "label.list.services": "Listar servi\u00e7os", +"label.list.vmware.vcenter.vms": "Listar Inst\u00e2ncias VMware", +"label.livepatch": "Aplicar patch ao vivo no(s) roteador(es) da Rede", "label.load.balancer": "Balanceador de carga", "label.loadbalancerinstance": "VMs designadas", "label.loadbalancerrule": "Regra de balanceamento de carga", @@ -974,18 +1384,23 @@ "label.local.storage.enabled.system.vms": "Habilitar armazenamento local para VMs de sistema", "label.localstorageenabled": "Habilitar armazenamento local para VMs de usu\u00e1rios", "label.localstorageenabledforsystemvm": "Habilitar armazenamento local para VMs de sistema", +"label.locked": "Bloqueado", "label.login": "Entrar", +"label.loginfo": "Informa\u00e7\u00e3o do arquivo de log", "label.login.portal": "Entrar no portal", "label.login.single.signon": "Single Sign-On", "label.logout": "Sair", "label.lun": "LUN", "label.lun.number": "LUN #", +"label.lxc": "LXC", "label.lxcnetworklabel": "R\u00f3tulo de tr\u00e1fego LXC", "label.macaddress": "Endere\u00e7o MAC", "label.macaddress.example": "O endere\u00e7o MAC. Exemplo: 01:23:45:67:89:ab", "label.macaddresschanges": "Mudan\u00e7as no endere\u00e7o MAC", "label.maclearning": "MAC learning", "label.macos": "MacOS", +"label.maintenance": "Manuten\u00e7\u00e3o", +"label.majorsequence": "Sequ\u00eancia Maior", "label.make": "Tornar", "label.make.project.owner": "Criar propriet\u00e1rio de conta de projeto", "label.make.user.project.owner": "Tornar usu\u00e1rio dono do projeto", @@ -993,16 +1408,28 @@ "label.manage": "Gerenciar", "label.manage.vpn.user": "Gerenciar usu\u00e1rios da VPN", "label.managed.instances": "Inst\u00e2ncias gerenciadas", +"label.managed.volumes": "Volumes Gerenciados", +"label.managementserverid": "Management Server", +"label.managementservername": "Management Server", "label.managedstate": "Estados gerenciados", "label.management": "Gerenciamento", "label.management.ips": "Gerenciamento de endere\u00e7os IP", -"label.management.server": "Servidor de gerenciamento", +"label.management.server": "Management Server", "label.management.servers": "Servidores de ger\u00eancia", "label.managementservers": "N\u00famero de servidores de ger\u00eancia", +"label.matchall": "Corresponder a todos", +"label.max": "M\u00e1x.", +"label.max.migrations": "M\u00e1x. de migra\u00e7\u00f5es", +"label.maxbackup": "M\u00e1x. de Backups", +"label.maxbackupstorage": "M\u00e1x. de Armazenamento de Backup (GiB)", +"label.maxbackups.to.retain": "M\u00e1x. de Backups para manter", +"label.maxbucket": "M\u00e1x. de Buckets", +"label.maxmembers": "M\u00e1x de membros", +"label.maxbackups": "Backups M\u00e1x.", "label.max.primary.storage": "M\u00e1x. de armazenamento prim\u00e1rio (GiB)", "label.max.secondary.storage": "M\u00e1x. de armazenamento secund\u00e1rio (GiB)", -"label.maxcpu": "N\u00famerom\u00e1ximo de cores de CPU", -"label.maxcpunumber": "N\u00famerom\u00e1ximo de cores de CPU", +"label.maxcpu": "N\u00famero m\u00e1ximo de cores de CPU", +"label.maxcpunumber": "N\u00famero m\u00e1ximo de n\u00facleos de CPU", "label.maxdatavolumeslimit": "Limite m\u00e1ximo de volume de dados", "label.maxerrorretry": "Limite de tentativas de recupera\u00e7\u00e3o de erro", "label.maxguestslimit": "Limite m\u00e1x. de guest", @@ -1010,8 +1437,9 @@ "label.maximum": "M\u00e1ximo", "label.maxinstance": "Inst\u00e2ncias m\u00e1x", "label.maxiops": "M\u00e1x IOPS", -"label.maxmemory": "M\u00e1x. de mem\u00f3ria (MiB)", +"label.maxmemory": "M\u00e1ximo de mem\u00f3ria (MiB)", "label.maxnetwork": "M\u00e1x. de redes", +"label.maxobjectstorage": "M\u00e1x. de Object Storage (GiB)", "label.maxprimarystorage": "M\u00e1x. armazenamento prim\u00e1rio (GiB)", "label.maxproject": "Projetos m\u00e1x.", "label.maxpublicip": "M\u00e1x. IPs P\u00fablicos", @@ -1026,6 +1454,7 @@ "label.mb.memory": "MB de mem\u00f3ria", "label.memory": "Mem\u00f3ria (em MB)", "label.memory.free": "Mem\u00f3ria livre", +"label.memory.maximum.mb": "Mem\u00f3ria m\u00e1x (em MB)", "label.memory.total": "Mem\u00f3ria total", "label.memory.usage.info": "Informa\u00e7\u00f5es sobre o uso de mem\u00f3ria", "label.memory.used": "Mem\u00f3ria usada", @@ -1041,6 +1470,16 @@ "label.memused": "Uso de mem\u00f3ria", "label.menu.security.groups": "Grupos de seguran\u00e7a", "label.menu.service.offerings": "Oferta de servi\u00e7os", +"label.metadata": "Metadados", +"label.metadata.description": "Metadados do Objeto", +"label.metadata.upload.description": "Definir metadados para o objeto", +"label.migrate.instance.single.storage": "Migrar todo(s) o(s) volume(s) da Inst\u00e2ncia para um \u00fanico armazenamento prim\u00e1rio", +"label.migrate.instance.specific.storages": "Migrar volume(s) da Inst\u00e2ncia para armazenamentos prim\u00e1rios espec\u00edficos", +"label.migrate.with.storage": "Migrar com armazenamento", +"label.min_balance": "Saldo m\u00edn", +"label.minimumsemanticversion": "Vers\u00e3o sem\u00e2ntica m\u00ednima", +"label.minmembers": "M\u00edn membros", +"label.minorsequence": "Sequ\u00eancia Menor", "label.metrics": "M\u00e9tricas", "label.migrate.allowed": "Migra\u00e7\u00e3o permitida", "label.migrate.data.from.image.store": "Migrar dados para o armazenamento de imagens", @@ -1053,21 +1492,31 @@ "label.migrating.data": "Migrando dados", "label.min.balance": "Saldo m\u00ednimo", "label.min.past.hour": "minutos passados da \u00faltima hora", -"label.mincpunumber": "N\u00famero m\u00edn de n\u00facleos de CPU", +"label.mincpunumber": "N\u00famero m\u00ednimo de n\u00facleos de CPU", "label.minimum": "M\u00ed\u00adnimo", "label.miniops": "M\u00edn IOPS", "label.minmaxiops": "M\u00edn IOPS /m\u00e1x IOPS", -"label.minmemory": "M\u00edn mem\u00f3ria (em MB)", +"label.minmemory": "M\u00ednimo de mem\u00f3ria (em MB)", "label.minsize": "Tamanho m\u00ednimo", "label.minute.past.hour": "minuto(s) passado(s) da \u00faltima hora", +"label.mode": "Modo", "label.monday": "Segunda", "label.monitor": "Monitor", +"label.monitor.expected.code": "C\u00f3digo de Status HTTP Esperado", +"label.monitor.http.method": "M\u00e9todo HTTP", +"label.monitor.interval": "Intervalo de verifica\u00e7\u00e3o de sa\u00fade (seg)", +"label.monitor.retry": "Contagem de tentativas antes de marcar como inativo", +"label.monitor.timeout": "Timeout (seg)", +"label.monitor.type": "Tipo de Monitor", +"label.monitor.url": "Caminho da URL", "label.monthly": "Mensal", "label.more.access.dashboard.ui": "Mais sobre como acessar a UI da Dashboard", +"label.mount.sharedfs": "Montar Sistema de Arquivos Compartilhado via NFS", "label.move.down.row": "Mover uma c\u00e9lula para baixo", "label.move.to.bottom": "Mover para baixo", "label.move.to.top": "Mover para o topo", "label.move.up.row": "Mover uma c\u00e9lula para cima", +"label.my.isos": "Minhas ISOs", "label.my.templates": "Meus templates", "label.na": "N/D", "label.name": "Nome", @@ -1089,6 +1538,11 @@ "label.network.name": "Nome da rede", "label.network.offering": "Oferta de rede", "label.network.offerings": "Oferta de rede", +"label.network.permissions": "Permiss\u00f5es de rede", +"label.network.policy": "Pol\u00edtica de Rede", +"label.network.restart.required": "Reinicializa\u00e7\u00e3o de rede necess\u00e1ria", +"label.network.route.table": "Tabela de roteamento de rede", +"label.network.routing.policy": "Pol\u00edtica de roteamento de rede", "label.network.selection": "Sele\u00e7\u00e3o da rede", "label.network.service.providers": "Provedores de servi\u00e7os de rede", "label.network.usage.info": "Informa\u00e7\u00f5es sobre o uso de rede", @@ -1098,6 +1552,7 @@ "label.networkkbsread": "Leitura da rede", "label.networkkbswrite": "Escrita da rede", "label.networklimit": "Limites de rede", +"label.networkmode": "Modo de Rede", "label.networkname": "Nome da rede", "label.networkofferingdisplaytext": "Oferta de rede", "label.networkofferingid": "Oferta de rede", @@ -1109,26 +1564,35 @@ "label.networktype": "Tipo de rede", "label.networkwrite": "Escrita da rede", "label.new": "Novo", +"label.new.autoscale.vmgroup": "Novo Grupo de AutoScale", "label.new.instance.group": "Novo grupo de inst\u00e2ncia", "label.new.password": "Nova senha", "label.new.project": "Novo projeto", "label.new.secondaryip.description": "Insira um novo endere\u00e7o IP secund\u00e1rio", "label.new.tag": "Nova etiqueta", +"label.new.version.available": "Nova vers\u00e3o dispon\u00edvel", "label.new.vm": "Nova VM", "label.newdiskoffering": "Nova oferta", "label.newinstance": "Nova inst\u00e2ncia", "label.newname": "Novo nome", "label.next": "Pr\u00f3ximo", "label.nfs": "NFS", +"label.nfsmountopts": "Op\u00e7\u00f5es de montagem NFS", "label.nfsserver": "Servidor NFS", "label.nic": "NIC", "label.nicadaptertype": "Tipo de adaptador de rede", +"label.nicmultiqueuenumber" : "N\u00famero de multiqueues da NIC", +"label.nicmultiqueuenumber.tooltip" : "N\u00famero de multiqueues da NIC. Apenas suportado para KVM. O valor \"-1\" indica que o n\u00famero de multiqueues da NIC ser\u00e1 definido como o n\u00famero de vCPUs da Inst\u00e2ncia.", +"label.nicpackedvirtqueuesenabled" : "Virtqueues empacotadas da NIC ativadas", +"label.nicpackedvirtqueuesenabled.tooltip" : "Ativar ou n\u00e3o virtqueues empacotadas da NIC. Apenas suportado para KVM com QEMU >= 4.2.0 e Libvirt >=6.3.0.", "label.nics": "Adaptadores de rede", "label.no": "N\u00e3o", "label.no.data": "Sem dados para mostrar", "label.no.errors": "Sem erros recentes", "label.no.items": "Sem itens dispon\u00edveis", "label.no.matching.offering": "Nenhuma oferta correspondente encontrada", +"label.no.matching.network": "Nenhuma Rede correspondente encontrada", +"label.no.usage.records": "Nenhum registro de uso encontrado", "label.noderootdisksize": "Tamanho do disco ra\u00edz do nodo (em GB)", "label.nodiskcache": "Sem cache de disco", "label.none": "Nenhum", @@ -1136,14 +1600,38 @@ "label.not.found": "N\u00e3o encontrado", "label.not.suitable": "N\u00e3o apropriado", "label.notifications": "Notifica\u00e7\u00f5es", +"label.nsx": "NSX", +"label.nsx.provider": "Provedor NSX", +"label.nsx.provider.edgecluster": "Cluster de borda do provedor NSX", +"label.nsx.provider.hostname": "Hostname do provedor NSX", +"label.nsx.provider.name": "Nome do provedor NSX", +"label.nsx.provider.password": "Senha do provedor NSX", +"label.nsx.provider.port": "Porta do provedor NSX", +"label.nsx.provider.tier0gateway": "Gateway tier-0 do provedor NSX", +"label.nsx.provider.transportzone": "Zona de transporte do provedor NSX", +"label.nsx.provider.username": "Nome de usu\u00e1rio do provedor NSX", +"label.nsx.supports.internal.lb": "Ativar servi\u00e7o de LB interno NSX", +"label.nsx.supports.lb": "Ativar servi\u00e7o de LB NSX", "label.num.cpu.cores": "# de n\u00facleos da CPU", "label.number": "#Regra", "label.numretries": "N\u00famero de tentativas", "label.nvpdeviceid": "ID", +"label.oauth.configuration": "Configura\u00e7\u00e3o OAuth", +"label.oauth.verification": "Verifica\u00e7\u00e3o OAuth", +"label.object.storage" : "Armazenamento de Objetos (Object Storage)", +"label.object.presigned.url": "URL pr\u00e9-assinada", +"label.object.presigned.url.description" : "URL pr\u00e9-assinada do objeto para acess\u00e1-lo sem autentica\u00e7\u00e3o.", +"label.object.url.description" : "URL do objeto", +"label.objectlocking": "Bloqueio de Objeto", +"label.objectstoragelimit": "Limite de Armazenamento de Objetos (GiB)", +"label.objectstore" : "Armazenamento de Objetos", +"label.objectstore.search" : "Busca baseada em prefixo no diret\u00f3rio atual", +"label.objectstorageid": "Pool de Armazenamento de Objetos", "label.ocfs2": "OCFS2", "label.of": "de(a)", "label.of.month": "do m\u00eas", "label.offerha": "Oferta HA", +"label.offeringid": "ID da Oferta", "label.offeringtype": "Tipo da oferta de computa\u00e7\u00e3o", "label.ok": "OK", "label.only.end.date.and.time": "Apenas data e hor\u00e1rio final", @@ -1155,9 +1643,20 @@ "label.opendaylight.controllers": "Controladores OpenDaylight", "label.operation": "Opera\u00e7\u00e3o", "label.operation.status": "Estado da opera\u00e7\u00e3o", +"label.operator.greater": "Maior que", +"label.operator.greater.or.equal": "Maior ou igual a", +"label.operator.less": "Menor que", +"label.operator.less.or.equal": "Menor ou igual a", +"label.operator.equal": "Igual a", "label.optional": "Opcional", "label.order": "Ordenar", "label.oscategoryid": "Prefer\u00eancia de SO", +"label.oscategoryname": "Nome da categoria do SO", +"label.osdisplayname": "Nome do SO", +"label.osdistribution": "Distribui\u00e7\u00e3o do SO", +"label.osmappingcheckenabled": "Verificar nome do SO com hypervisor", +"label.osname": "Nome do SO", +"label.osnameforhypervisor": "Nome de mapeamento do hypervisor", "label.ostypeid": "Tipo de SO", "label.ostypename": "Tipo de SO", "label.other": "Outro", @@ -1171,9 +1670,11 @@ "label.override.guest.traffic": "Sobrescrever tr\u00e1fego guest", "label.override.public.traffic": "Sobrescrever tr\u00e1fego p\u00fablico", "label.override.rootdisk.size": "Sobrescrever tamanho do disco ra\u00edz", +"label.override.root.diskoffering": "Substituir oferta de disco root", "label.overrideguesttraffic": "Sobrescrever tr\u00e1fego guest", "label.overridepublictraffic": "Sobrescrever tr\u00e1fego p\u00fablico", "label.ovf.properties": "Propriedades vApp", +"label.ovm3": "OVM3", "label.ovm3cluster": "Clustering nativo", "label.ovm3networklabel": "Label de trafego OVM3", "label.ovm3pool": "Pooling nativo", @@ -1187,27 +1688,36 @@ "label.page": "p\u00e1gina", "label.palo.alto.firewall": "Firewall Palo Alto", "label.palp": "Perfil de log Palo Alto", +"label.param.name": "Nome do par\u00e2metro", +"label.param.value": "Valor do par\u00e2metro", "label.params": "Par\u00e2metros", "label.parentdomainname": "Dom\u00ednio pai", "label.parentname": "Pai", +"label.parentsubnet": "Sub-rede Pai", "label.passive": "Passivo", "label.password": "Senha", +"label.password.default": "Senha Padr\u00e3o", "label.password.reset.confirm": "A senha foi recuperada para", +"label.password.tooltip": "A senha para o Host", "label.passwordenabled": "Habilitar troca de senha", "label.path": "Caminho (Path)", "label.patp": "Perfil de amea\u00e7a Palo Alto", "label.pavr": "Roteador virtual", "label.payload": "Payload", +"label.payloadurl": "URL de Payload", "label.pcidevice": "GPU", +"label.pending.jobs": "Jobs pendentes", "label.per.account": "Por conta", "label.per.zone": "Por zona", "label.percentage": "Porcentagem", +"label.performfreshchecks": "Executar novas verifica\u00e7\u00f5es", "label.perfectforwardsecrecy": "Perfect Forward secrecy", "label.perform.fresh.checks": "Realizar novas verifica\u00e7\u00f5es", "label.permission": "Permiss\u00e3o", "label.permissions": "permiss\u00e3o", "label.physical.network": "Rede f\u00edsica", "label.physicalnetworkid": "Rede f\u00edsica", +"label.physicalnetworkname": "Nome da Rede F\u00edsica", "label.physicalsize": "Tamanho f\u00edsico", "label.ping.path": "Caminho do ping", "label.pkcs.private.certificate": "Certificado privado PKCS#8", @@ -1221,6 +1731,8 @@ "label.podid": "Pod", "label.podname": "Nome do pod", "label.pods": "Pods", +"label.policy": "Pol\u00edtica", +"label.policyuuid": "Pol\u00edtica de Rede", "label.port": "Porta", "label.port.range": "Intervalo de porta", "label.portforwarding": "Encaminhamento de porta", @@ -1231,20 +1743,30 @@ "label.powerflex.storage.pool": "Pool de armazenamento", "label.powerstate": "Estado de energia", "label.preferred": "Preferido", +"label.prefix": "Prefixo", +"label.prefix.type": "Tipo de Prefixo", +"label.prepare.for.shutdown": "Preparar para Desligamento", +"label.prepareformaintenance": "Preparar para Manuten\u00e7\u00e3o", "label.presetup": "PreSetup", "label.prev": "Anterior", "label.previous": "Anterior", +"label.primera.url.tooltip": "URL designando o endpoint do array de armazenamento Primera, formatado como: http[s]://HOSTNAME:PORT?cpg=NAME&hostset=NAME[&skipTlsValidation=true][&snapCPG=NAME][&taskWaitTimeoutMs=#][&keyttl=#][&connectTimeoutMs=#] onde valores em [] s\u00e3o opcionais.", +"label.primera.username.tooltip": "O nome de usu\u00e1rio com privil\u00e9gios de edi\u00e7\u00e3o", +"label.flashArray.username.tooltip": "O nome de us\u00e1rio com privil\u00e9gios de edi\u00e7\u00e3o", +"label.flashArray.url.tooltip": "URL designando o endpoint Flash Array, formatado como: http[s]://HOSTNAME:PORT?pod=NAME&hostgroup=NAME[&skipTlsValidation=true][&postCopyWaitMs=#][&keyttl=#][&connectTimeoutMs=#][&apiLoginVersion=#][&apiVersion=#] onde valores em [] s\u00e3o opcionais.", "label.primary": "Prim\u00e1rio", "label.primary.storage": "Armazenamento prim\u00e1rio", "label.primary.storage.allocated": "Armazenamento prim\u00e1rio alocado", "label.primary.storage.used": "Uso do armazenamento prim\u00e1rio", "label.primarystoragelimit": "Limites do armazenamento prim\u00e1rio (GiB)", +"label.primarystoragetotal": "Armazenamento prim\u00e1rio", "label.private.gateway": "Gateway privado", "label.private.interface": "Interface privada", "label.private.registry": "Registro privado", "label.privateinterface": "Interface privada", "label.privateip": "Endere\u00e7o IP privado", "label.privatekey": "Chave privada PKCS#8", +"label.privatemtu": "MTU da Interface Privada", "label.privatenetwork": "Rede privada", "label.privateport": "Porta privada", "label.profilename": "Perfil", @@ -1279,15 +1801,22 @@ "label.public.ips": "IPs P\u00fablicos", "label.public.lb": "LB p\u00fablico", "label.public.traffic": "Tr\u00e1fego p\u00fablico", +"label.public.traffic.nsx": "Tr\u00e1fego P\u00fablico NSX", +"label.publicipid": "ID do endere\u00e7o IP", +"label.publicmtu": "MTU da Interface P\u00fablica", "label.publicinterface": "Interface p\u00fablica", "label.publicip": "Endere\u00e7o IP", "label.publickey": "Chave p\u00fablica", "label.publicnetwork": "Rede p\u00fablica", "label.publicport": "Porta p\u00fablica", +"label.purge.usage.records.error": "Falha ao eliminar registros de uso", +"label.purge.usage.records.success": "Registros de uso eliminados com sucesso", +"label.purgeresources": "Limpar Recursos", "label.purpose": "Prop\u00f3sito", "label.qostype": "Tipo de QoS", "label.quickview": "Visualiza\u00e7\u00e3o r\u00e1pida", "label.quiescevm": "Quiesce VM", +"label.quiettime": "Tempo em espera (em seg)", "label.quota": "Cota", "label.quota.add.credits": "Adicionar cr\u00e9ditos", "label.quota.configuration": "Configura\u00e7\u00e3o da cota", @@ -1300,6 +1829,7 @@ "label.quota.statement.tariff": "Tarifa", "label.quota.summary": "Relat\u00f3rios", "label.quotastate": "Estado da cota", +"label.quota_enforce": "Impor Cota", "label.summary": "Sum\u00e1rio", "label.quota.tariff": "Tarifa", "label.quota.tariff.activationrule": "Regra de ativa\u00e7\u00e3o", @@ -1309,15 +1839,24 @@ "label.quota.total": "Total", "label.quota.type.name": "Tipo de uso", "label.quota.type.unit": "Unidade do uso", +"label.action.update.object.storage" : "Atualizar Object Storage", "label.quota.usage": "Consumo da cota", "label.quota.validate.activation.rule": "Validar regra de ativa\u00e7\u00e3o", "label.quota.value": "Valor", "label.rados.monitor": "Monitor RADOS", +"label.rados.monitor.description": "O(s) monitor(es) RADOS. Se houver v\u00e1rios monitores, eles s\u00e3o separados por v\u00edrgula. Por exemplo, \"192.168.0.1,192.168.0.2,192.168.0.3\", \"mon1, mon2, mon3\". Endere\u00e7os IPv6 devem incluir colchetes, por exemplo, \"[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]\".", "label.rados.pool": "Pool do RADOS", "label.rados.secret": "Segredo RADOS", "label.rados.user": "Usu\u00e1rio RADOS", "label.ram": "RAM", +"label.range.last.1month": "\u00daltimo 1 m\u00eas", +"label.range.last.1week": "\u00daltima 1 semana", +"label.range.last.2week": "\u00daltimas 2 semanas", +"label.range.last.3month": "\u00daltimos 3 meses", +"label.range.today": "Hoje", +"label.range.yesterday": "Ontem", "label.raw.data": "Dados Brutos", +"label.rawusage": "Uso bruto (em horas)", "label.rbd": "RDB", "label.rbdid": "Usu\u00e1rio Ceph", "label.rbdmonitor": "Monitor Ceph", @@ -1329,8 +1868,14 @@ "label.readonly": "Apenas leitura", "label.reason": "Motivo", "label.reboot": "Reiniciar", +"label.recent.deliveries": "Entregas recentes", +"label.recover.sharedfs": "Recuperar Sistema de Arquivos Compartilhado", +"label.recovering": "Recuperando", "label.receivedbytes": "Bytes recebidos", "label.recover.vm": "Recuperar VM", +"label.recursivedomains": "Dom\u00ednios recursivos", +"label.redeliver": "Reentregar", +"label.redirecturi": "URI de Redirecionamento", "label.redirect": "Clique para acessar:", "label.redundantrouter": "Roteador Redundante", "label.redundantrouterstate": "Estado redundante", @@ -1338,40 +1883,61 @@ "label.redundantvpcrouter": "VPC redundante", "label.refresh": "Atualizar", "label.region": "Regi\u00e3o", +"label.register.api.key": "Registrar chave de API", +"label.register.oauth": "Registrar OAuth", +"label.register.user.data": "Registrar dados de usu\u00e1rio", "label.register.template": "Registrar template", "label.reinstall.vm": "Reinstalar VM", "label.reject": "Rejeitar", "label.related": "Relacionado", +"label.relationaloperator": "Operador", "label.release": "Liberar", "label.release.account": "Liberar conta", +"label.release.dedicated.bgp.peer": "Liberar par BGP dedicado", "label.release.dedicated.cluster": "Liberar cluster dedicado", "label.release.dedicated.host": "Liberar host dedicado", +"label.release.dedicated.ipv4.subnet": "Liberar sub-rede IPv4 dedicada", "label.release.dedicated.pod": "LIberar pod dedicado", "label.release.dedicated.zone": "Liberar zona dedicada", "label.releasing.ip": "Liberando IP", +"label.remote.instances": "Inst\u00e2ncias Remotas", "label.remove": "Remover", "label.remove.annotation": "Remover coment\u00e1rio", +"label.remove.bgp.peer": "Remover par BGP", "label.remove.egress.rule": "Remover regra de sa\u00edda", +"label.remove.gui.theme": "Remover tema da GUI", +"label.remove.interface.route.table": "Remover tabela de rotas de interface Tungsten", "label.remove.ip.range": "Remover intervalo de IPs", +"label.remove.ipv4.subnet": "Remover sub-rede IPv4", "label.remove.ldap": "Remover LDAP", +"label.remove.logical.network": "Remover Rede do roteador l\u00f3gico", +"label.remove.logical.router": "Remover roteador l\u00f3gico", "label.remove.network.offering": "Remover oferta de rede", +"label.remove.network.route.table": "Remover tabela de roteamento de Rede Tungsten Fabric", "label.remove.pf": "Remover regra de redirecionamento de porta", +"label.remove.policy": "Remover pol\u00edtica", "label.remove.project.account": "Remover conta de projeto", "label.remove.project.role": "Remover fun\u00e7\u00e3o do projeo", "label.remove.project.user": "Remover usu\u00e1rio do projeto", +"label.remove.routing.policy": "Remover pol\u00edtica de roteamento Tungsten-Fabric", +"label.remove.tungsten.tag": "Remover Tag", +"label.remove.user.data": "Remover Userdata", +"label.removed": "Removido", +"label.removedaccounts": "Contas removidas", "label.remove.rule": "Remover regra", "label.remove.ssh.key.pair": "Remover par de chaves SSH", "label.remove.vm.from.lb": "Remover VM da regra de balanceamento de carga", "label.remove.vmware.datacenter": "Remover datacenter VMware", "label.remove.vpc": "Remover VPC", "label.remove.vpc.offering": "Remover oferta VPC", -"label.removed": "Removido", "label.removing": "Removendo", "label.replace.acl": "Substituir ACL", "label.report.bug": "Reportar um problema", +"label.request": "Solicita\u00e7\u00e3o", "label.required": "Obrigat\u00f3rio", "label.requireshvm": "HVM", "label.requiresupgrade": "Requer atualiza\u00e7\u00e3o", +"label.reserved": "Reservado", "label.reserved.system.gateway": "Gateway de sistema reservado", "label.reserved.system.ip": "IP de sistema reservado", "label.reserved.system.netmask": "M\u00e1scara de rede reservada do sistema", @@ -1381,8 +1947,11 @@ "label.reservedsystemnetmask": "M\u00e1scara de rede reservada do sistema", "label.reservedsystemstartip": "In\u00edcio dos IPs reservados para o sistema", "label.reset": "Reiniciar", +"label.reset.config.value": "Redefinir para valor padr\u00e3o", "label.reset.ssh.key.pair": "Recriar par de chaves SSH", "label.reset.to.default": "Reinicializar para o padr\u00e3o", +"label.reset.userdata.on.autoscale.vm.group": "Redefinir Userdata no Grupo de VMs de AutoScale", +"label.reset.userdata.on.vm": "Redefinir Userdata na Inst\u00e2ncia", "label.reset.vpn.connection": "Reiniciar a conex\u00e3o VPN", "label.resource": "Recurso", "label.resource.limit.exceeded": "Limite de recurso excedido", @@ -1391,6 +1960,8 @@ "label.resourcename": "Nome do recurso", "label.resources": "Recursos", "label.resourcestate": "Estado do recurso", +"label.resourcetype": "Tipo de recurso", +"label.response": "Resposta", "label.restart.network": "Reiniciar rede", "label.restart.vpc": "Reiniciar a VPC", "label.restartrequired": "Reiniciar obrigat\u00f3rio", @@ -1398,12 +1969,18 @@ "label.restore.volume.attach": "Restaurar volume e anex\u00e1-lo", "label.review": "Revisar", "label.role": "Fun\u00e7\u00e3o", +"label.roleid": "Fun\u00e7\u00e3o", "label.rolename": "Fun\u00e7\u00e3o", "label.roles": "Fun\u00e7\u00f5es", "label.roletype": "Tipo de fun\u00e7\u00e3o", "label.rolepermissiontab.searchbar": "Pesquisa de regras", "label.root.certificate": "Certificado ra\u00edz", "label.root.disk.size": "Tamanho do disco ra\u00edz (GB)", +"label.rootdiskcontrollertypekvm": "Controladora do disco ROOT", +"label.rootdisksize": "Tamanho do disco ROOT (GB)", +"label.routenexthop": "Pr\u00f3ximo salto da rota", +"label.routenexthoptype": "Tipo do pr\u00f3ximo salto da rota", +"label.routeprefix": "Prefixo da rota", "label.rootdisk": "Disco ra\u00edz", "label.rootdiskcontrollertype": "Controlador do disco ra\u00edz", "label.router.health.check.last.updated": "\u00daltima atualiza\u00e7\u00e3o", @@ -1413,6 +1990,12 @@ "label.routercount": "Total de roteadores virtuais", "label.routerip": "Endere\u00e7os IPv4 para o roteador dentro da rede compartilhada", "label.routeripv6": "Endere\u00e7os IPv6 para o roteador dentro da rede compartilhada", +"label.router.source.nat.ip": "IP source NAT do roteador", +"label.routing.firewall": "Firewall de Roteamento IPv4", +"label.routing.policy": "Pol\u00edtica de roteamento", +"label.routing.policy.terms": "Termos da pol\u00edtica de roteamento", +"label.routing.policy.terms.then": "Termos da pol\u00edtica de roteamento ent\u00e3o", +"label.routingmode": "Modo de roteamento", "label.resourcegroup": "Grupo de recurso", "label.rule": "Regra", "label.rule.number": "Regra n\u00famero", @@ -1422,6 +2005,7 @@ "label.rules.file.to.import": "Arquivo CSV com as defini\u00e7\u00f5es de regras para importar", "label.run.proxy.locally": "Rodar o proxy localmente", "label.running": "VMs rodando", +"label.running.vms": "Inst\u00e2ncias em Execu\u00e7\u00e3o", "label.s2scustomergatewayid": "ID do gateway do cliente site a site", "label.s2svpngatewayid": "ID do gateway da VPN site a site", "label.s3.access.key": "Chave de acesso", @@ -1442,9 +2026,17 @@ "label.save": "Salvar", "label.save.new.rule": "Salvar nova regra", "label.scale.vm": "Escalar VM", +"label.scaledown.policies": "Pol\u00edticas de ScaleDown", +"label.scaledown.policy": "Pol\u00edtica de ScaleDown", +"label.scaleup.policies": "Pol\u00edticas de ScaleUp", +"label.scaleup.policy": "Pol\u00edtica de ScaleUp", +"label.scaling": "Escalonamento", +"label.schedule.add": "Adicionar agendamento", +"label.schedules": "Agendamentos", "label.schedule": "Programar", "label.scheduled.backups": "Backups programados", "label.scope": "Escopo", +"label.scope.tooltip": "Escopo do Pool de Armazenamento Prim\u00e1rio", "label.search": "Pesquisar", "label.secondary.isolated.vlan.type.isolated": "Isolada", "label.secondary.isolated.vlan.type.promiscuous": "Prom\u00edscuos", @@ -1453,11 +2045,13 @@ "label.secondaryips": "IPs secund\u00e1rios", "label.secondarystoragelimit": "Limites do armazenamento secund\u00e1rio (GiB)", "label.secretkey": "Chave secreta", +"label.secret.key": "Chave secreta", "label.secured": "Protegido", "label.security.groups": "Grupos de seguran\u00e7a", "label.securitygroup": "Grupo de seguran\u00e7a", "label.securitygroupenabled": "Grupo de seguran\u00e7a ativado", "label.securitygroups": "Grupos de seguran\u00e7a", +"label.securitygroupsenabled": "Grupos de seguran\u00e7a ativados", "label.see.more.info.cpu.usage": "Ver mais informa\u00e7\u00f5es sobre o uso de CPU", "label.see.more.info.memory.usage": "Ver mais informa\u00e7\u00f5es sobre o uso de mem\u00f3ria", "label.see.more.info.network.usage": "Ver mais informa\u00e7\u00f5es sobre o uso de rede", @@ -1465,11 +2059,17 @@ "label.see.more.info.shown.charts": "Ver mais informa\u00E7\u00F5es sobre os gr\u00E1ficos mostrados", "label.select": "Selecionar", "label.select-view": "Selecionar visualiza\u00e7\u00e3o", +"label.select.2fa.provider": "Selecione o provedor", "label.select.a.zone": "Selecione uma zona", "label.select.deployment.infrastructure": "Selecione uma infraestrutura de implanta\u00e7\u00e3o", +"label.select.guest.os.type": "Por favor, selecione o tipo de SO convidado", +"label.select.network": "Selecionar Rede", "label.select.period": "Selecionar per\u00edodo", "label.select.project": "Selecionar projeto", "label.select.projects": "Selecionar projetos", +"label.select.ps": "Selecionar armazenamento prim\u00e1rio", +"label.select.root.disk": "Selecione o disco ROOT", +"label.select.source.vcenter.datacenter": "Selecione o Datacenter VMware vCenter de origem", "label.select.tier": "Selecionar camada", "label.select.zones": "Selecionar zonas", "label.selected.storage": "Armazenamento selecionado", @@ -1478,10 +2078,12 @@ "label.semanticversion": "Vers\u00e3o sem\u00e2ntica", "label.sent": "Enviado", "label.sentbytes": "Bytes enviados", +"label.sequence": "Sequ\u00eancia", "label.server": "Servidor", "label.server.certificate": "Certificados do servidor", "label.service.connectivity.distributedroutercapabilitycheckbox": "Roteador distribu\u00eddo", "label.service.connectivity.regionlevelvpccapabilitycheckbox": "VPC a n\u00edvel de regi\u00e3o", +"label.service.group": "Grupo de servi\u00e7os", "label.service.lb.elasticlbcheckbox": "LB el\u00e1stico", "label.service.lb.inlinemodedropdown": "Modo", "label.service.lb.lbisolationdropdown": "Isolamento de LB", @@ -1490,23 +2092,31 @@ "label.service.offering": "Plano", "label.service.staticnat.associatepublicip": "Associa IP p\u00fablico", "label.service.staticnat.elasticipcheckbox": "IP el\u00e1stico", +"label.servicegroupuuid": "Grupo de Servi\u00e7os", +"label.serviceip": "IP de Servi\u00e7o", "label.servicelist": "Servi\u00e7os", "label.serviceofferingid": "Oferta de computa\u00e7\u00e3o", "label.serviceofferingname": "Oferta de computa\u00e7\u00e3o", +"label.sessions": "Sess\u00f5es de cliente ativas", "label.set.default.nic": "Configurar para NIC padr\u00e3o", "label.set.reservation": "Fazer reserva", +"label.set.reservation.account.desc": "Por favor, especifique uma Conta para ser associada a este intervalo de IP", "label.set.reservation.desc": "(opcional) especificar uma conta a ser associada a esta faixa de IP.

VMs de sistema: habilitar a dedica\u00e7\u00e3o da faixa IP p\u00fablica para SSVM e CPVM, campo de conta desativado. Rigor das reservas definido em 'system.vm.public.ip.reservation.mode.strictness'", +"label.set.reservation.systemvm.desc": "Ativar dedica\u00e7\u00e3o de intervalo de IP p\u00fablico para SSVM e CPVM. Rigor da reserva definido em 'system.vm.public.ip.reservation.mode.strictness'.", "label.setting": "Configura\u00e7\u00e3o", "label.settings": "Configura\u00e7\u00f5es", "label.setup": "Configura\u00e7\u00e3o", -"label.shared": "Compartilhado", -"label.sharedexecutable": "Compartilhado", +"label.shared": "Compartilhado(a)", +"label.shared.filesystems": "Sistemas de Arquivos Compartilhados", +"label.sharedexecutable": "Execut\u00e1veis compartilhados ", "label.sharedmountpoint": "SharedMountPoint", "label.sharedrouterip": "Endere\u00e7os IPv4 para o roteador dentro da rede compartilhada", "label.sharedrouteripv6": "Endere\u00e7os IPv6 para o roteador dentro da rede compartilhada", "label.sharewith": "Compartilhar com", +"label.show.usage.records": "Mostrar registros de uso", "label.showing": "Exibindo", "label.shrinkok": "Encolhimento OK", +"label.shutdown": "Desligar", "label.shutdown.provider": "Desabilitar provider", "label.simplified.chinese.keyboard": "Teclado chin\u00eas simplificado", "label.site.to.site.vpn": "VPN site a site", @@ -1524,19 +2134,35 @@ "label.snapshotlimit": "Limite de snapshots", "label.snapshotmemory": "Snapshot da mem\u00f3ria", "label.snapshots": "Snapshots", +"label.snapshottype": "Tipo de Snapshot", +"label.softwareversion": "Vers\u00e3o do software", "label.sockettimeout": "Tempo limite no socket", +"label.source": "Selecionar Hypervisor de Origem de Importa\u00e7\u00e3o-Exporta\u00e7\u00e3o", "label.source.based": "SourceBased", "label.sourcecidr": "CIDR de origem", "label.sourcecidrlist": "Lista de CIDRs de origem", +"label.sourcehost": "Host de origem", "label.sourceipaddress": "Endere\u00e7o IP de origem", +"label.sourceipaddressnetworkid": "ID da Rede do endere\u00e7o IP de origem", "label.sourcenat": "Source NAT", +"label.sourcenatipaddress": "Endere\u00e7o IP de NAT de origem", "label.sourcenatsupported": "Suporte \u00e0 source NAT", "label.sourcenattype": "Tipo de source NAT suportado", "label.sourceport": "Porta de origem", +"label.sourcetype": "Tipo de origem", +"label.specifyasnumber": "Especificar N\u00famero AS", "label.specifyipranges": "Especifique range de IP", "label.specifyvlan": "Especificar VLAN", "label.splitconnections": "Separar conex\u00f5es", "label.sr.name": "Nome da etiqueta SR", +"label.srcaddressgroupuuid": "Grupo de Endere\u00e7os de Origem", +"label.srcendport": "Porta Final de Origem", +"label.srcipprefix": "Endere\u00e7o de Rede de Origem", +"label.srcipprefixlen": "Comprimento do Prefixo de Origem", +"label.srcnetwork": "Rede de Origem", +"label.srcnetworkuuid": "Rede", +"label.srcstartport": "Porta Inicial de Origem", +"label.srctaguuid": "Tag de Origem", "label.srx": "SRX", "label.srx.firewall": "Juniper SRX firewall", "label.ssh.key.pairs": "Par de chaves SSH", @@ -1545,6 +2171,7 @@ "label.sshkeypairs": "Par de chaves SSH", "label.standard.us.keyboard": "Teclado padr\u00e3o (EUA)", "label.sslcertificates": "Certificados SSL", +"label.sslverification": "Verifica\u00e7\u00e3o SSL", "label.start": "Iniciar", "label.start.date": "Data de in\u00edcio", "label.start.date.and.time": "Data e hor\u00e1rio inicial", @@ -1553,14 +2180,18 @@ "label.start.reserved.system.ip": "In\u00edcio dos IPs reservados para o sistema", "label.start.rolling.maintenance": "Iniciar a manunten\u00e7\u00e3o", "label.start.vm": "Iniciar VM", +"label.startasn": "Iniciar N\u00famero AS", "label.startdate": "Por data (in\u00edcio)", +"label.starting": "Iniciando", "label.startip": "IP do in\u00edcio", "label.startipv4": "IP inicial IPv4", "label.startipv6": "IP inicial IPv6", "label.startport": "Porta de in\u00edcio", "label.startquota": "Valor", "label.state": "Estado", +"label.static": "Est\u00e1tico", "label.static.routes": "Rotas est\u00e1ticas", +"label.staticnat": "NAT Est\u00e1tico", "label.statistics": "Estat\u00edsticas", "label.status": "Estado", "label.step.1": "Passo 1", @@ -1583,6 +2214,8 @@ "label.sticky.tablesize": "Tamanho da tabela", "label.stop": "Parar", "label.stopped": "VMs paradas", +"label.stopped.vms": "Inst\u00e2ncias Paradas", +"label.stopping": "Parando", "label.storage": "Armazenamento", "label.storage.migration.required": "Migra\u00e7\u00e3o de armazenamento necess\u00e1ria", "label.storage.tags": "Tags de armazenamento", @@ -1591,34 +2224,56 @@ "label.storagemotionenabled": "Motion do armazenamento habilitado", "label.storagepolicy": "Pol\u00edtica de armazenamento", "label.storagepool": "Pool de armazenamento", +"label.storagepool.tooltip": "Pool de Armazenamento de Destino. O volume deve estar localizado neste Pool de Armazenamento", "label.storagetags": "Tags de armazenamento", "label.storagetype": "Tipo de armazenamento", "label.storageip": "Endere\u00e7o IP na rede de armazenamento", "label.strict": "Rigoroso", "label.subdomainaccess": "acesso ao subdom\u00ednio", "label.submit": "Enviar", +"label.subnet": "Sub-rede", "label.succeeded": "Sucedido", "label.success": "Sucesso", +"label.success.migrations": "Migra\u00e7\u00f5es bem-sucedidas", "label.success.set": "Definido com sucesso", "label.success.updated": "Atualizado com sucesso", "label.suitability": "Adequabilidade", "label.suitable": "Adequado", "label.sunday": "Domingo", +"label.supported": "Suportado", "label.supportedservices": "Servi\u00e7os suportados", "label.supportsautoscaling": "Suporte \u00e0 auto-escala", "label.supportsha": "Suporte \u00e0 HA", "label.supportspublicaccess": "Suporte \u00e0 acesso p\u00fablico", "label.supportsstrechedl2subnet": "Suporte \u00e0 Streched L2 Subnet", +"label.supportsvmautoscaling": "Suporta escalonamento autom\u00e1tico", "label.suspend.project": "Suspender projeto", "label.switch.type": "Tipo de switch", "label.sync.storage": "Sincronizar pool do armazenamento", +"label.system.ip.pool": "Pool do Sistema", "label.system.offering": "Ofertas de sistema", "label.system.offerings": "Ofertas de sistema", "label.system.service.offering": "Ofertas de servi\u00e7o de sistema", "label.system.vm": "VM de sistema", "label.system.vms": "VMs de sistema", +"label.systemcycleusage": "Ciclos de usu\u00e1rio, sistema e ociosidade", +"label.systemloadaverages": "M\u00e9dias de carga de 1, 5 e 15 minutos", +"label.systemmemoryfree": "Mem\u00f3ria livre do sistema", +"label.systemmemorytotal": "Mem\u00f3ria total do sistema", +"label.systemmemoryused": "Mem\u00f3ria do sistema usada", +"label.systemmemoryvirtualsize": "Tamanho virtual total do processo", +"label.systemtotalcpucycles": "Capacidade total da CPU para todos os n\u00facleos em MHz", +"label.systemvm": "VM de Sistema", "label.systemvmtype": "Tipo de VM de sistema", +"label.tag": "Tag", "label.tag.key": "Chave", +"label.tag.nsx": "nsx", +"label.tag.systemvm": "systemvm", +"label.tag.type": "Tipo de Tag", +"label.tagged.limits": "Limites marcados", +"label.tagtypeuuid": "Tipo de Tag", +"label.taguuid": "Tag", +"label.taken": "Ocupado", "label.tag.value": "Valor", "label.tagged": "Etiquetado", "label.tags": "Etiquetas", @@ -1638,7 +2293,13 @@ "label.templatename": "Template", "label.templates": "Templates", "label.templatesubject": "Assunto", +"label.templatetag": "Tag", "label.templatetype": "Tipo de template", +"label.templateremoved": "Este template foi removido", +"label.templateversion": "Vers\u00e3o do template", +"label.term.type": "Tipo de termo", +"label.test": "Teste", +"label.test.webhook.delivery": "Testar Entrega de Webhook", "label.tftpdir": "Diret\u00f3rio raiz do TFTP", "label.theme.alert": "O painel de configura\u00e7\u00e3o \u00e9 apenas vis\u00edvel no ambiente de desenvolvimento, por favor, salve as configura\u00e7\u00f5es para que as mudan\u00e7as tenham efeito.", "label.theme.color": "Cor do tema", @@ -1648,12 +2309,23 @@ "label.theme.page.style.setting": "Defini\u00e7\u00e3o do estilo da p\u00e1gina", "label.theme.project": "Estilo do projeto", "label.theme.project.navigation.setting": "Configura\u00e7\u00e3o de navega\u00e7\u00e3o do projeto", +"label.threadsblockedcount": "Threads Bloqueadas", +"label.threadsdeamoncount": "Threads Daemon", +"label.threadsnewcount": "Novas Threads", +"label.threadsrunnablecount": "Threads Execut\u00e1veis", +"label.threadsterminatedcount": "Threads Terminadas", +"label.threadstotalcount": "Contagem total de Threads", +"label.threadswaitingcount": "Threads Aguardando", +"label.threshold.description": "Valor pelo qual o Contador ser\u00e1 avaliado com o Operador selecionado", +"label.themes": "Temas", "label.threshold": "Limiar", "label.thursday": "Quinta", "label.time": "Tempo", "label.timeout": "Timeout", "label.timeout.in.second ": " Timeout (segundos)", "label.timezone": "Fuso hor\u00e1rio", +"label.tmppath": "Caminho Temp", +"label.tmppath.tooltip": "Caminho tempor\u00e1rio para armazenar imagens de disco no Host Externo antes de copiar para o pool de armazenamento de destino. O padr\u00e3o \u00e9 /tmp", "label.to": "para", "label.token": "Token", "label.token.for.dashboard.login": "Token para o login na Dashboard pode ser obtido atrav\u00e9s do seguinte comando", @@ -1667,34 +2339,82 @@ "label.traffic.types": "Tipos de tr\u00e1fego", "label.traffictype": "Tipo de tr\u00e1fego", "label.transportzoneuuid": "UUID da zona de transporte", +"label.trigger.shutdown": "Acionar Desligamento Seguro", "label.try.again": "Tente novamente", "label.tuesday": "Ter\u00e7a", +"label.2FA": "2FA", +"label.two.factor.authentication": "Autentica\u00e7\u00e3o de Dois Fatores", +"label.two.factor.authentication.secret.key": "Sua chave secreta de autentica\u00e7\u00e3o de dois fatores", +"label.two.factor.authentication.static.pin": "Seu PIN est\u00e1tico de autentica\u00e7\u00e3o de dois fatores", +"label.tungsten.fabric": "Tungsten Fabric", +"label.tungsten.fabric.provider": "Provedor Tungsten Fabric", +"label.tungsten.fabric.routing": "Roteamento Tungsten Fabric", +"label.tungsten.interface.router.table": "Tabela de rotas da interface", +"label.tungsten.logical.router": "Roteador L\u00f3gico", +"label.tungsten.network.router.table": "Tabela de rotas da rede", +"label.tungsten.provider": "Provedor Tungsten", +"label.tungsten.provider.gateway": "Gateway do provedor Tungsten", +"label.tungsten.provider.hostname": "Hostname do provedor Tungsten", +"label.tungsten.provider.introspectport": "Porta de introspec\u00e7\u00e3o do provedor Tungsten", +"label.tungsten.provider.name": "Nome do provedor Tungsten", +"label.tungsten.provider.port": "Porta do provedor Tungsten", +"label.tungsten.provider.vrouterport": "Porta do vrouter do provedor Tungsten", +"label.tungsten.router.table": "Tabela de Roteador", +"label.tungsten.routing.polices": "Pol\u00edticas de roteamento", +"label.tungsten.static.routes": "Rotas Est\u00e1ticas", +"label.tungstengateway": "Gateway", +"label.tungsteninterfaceroutetablename": "Nome", +"label.tungstennetworkroutetablename": "Nome", +"label.tungstenproviderhostname": "Hostname do provedor", +"label.tungstenproviderintrospectport": "Porta de introspec\u00e7\u00e3o do provedor", +"label.tungstenproviderport": "Porta do provedor", +"label.tungstenprovideruuid": "UUID do Provedor", +"label.tungstenprovidervrouterport": "Porta do vrouter do provedor", +"label.tungstenroutingpolicyterm": "Rede", +"label.tungstenvms": "Inst\u00e2ncias", "label.type": "Tipo", "label.type.id": "Tipo do ID", "label.ucs": "UCS", "label.udp": "UDP", +"label.udp6": "UDPv6", +"label.uefi.supported": "UEFI suportado", "label.uk.keyboard": "Teclado do Reino Unido", "label.unauthorized": "N\u00e3o autorizado", "label.unavailable": "Indispon\u00edvel", +"label.undefined": "Indefinido", "label.unit": "Unidade", "label.unknown": "Desconhecido", "label.unlimited": "Ilimitado", +"label.unmanaged": "N\u00e3o gerenciado", "label.unmanage.instance": "N\u00e3o gerenciar inst\u00e2ncia", +"label.unmanage.volume": "Parar de gerenciar Volume", "label.unmanaged.instance": "Inst\u00e2ncia n\u00e3o gerenciada", "label.unmanaged.instances": "Inst\u00e2ncias n\u00e3o gerenciadas", +"label.unmanaged.volumes": "Volumes n\u00e3o gerenciados", "label.untagged": "N\u00e3omarcado", +"label.up": "Cima", +"label.update.autoscale.vmgroup": "Atualizar Grupo de AutoScale", +"label.update.bgp.peer": "Atualizar par BGP", +"label.update.condition": "Atualizar condi\u00e7\u00e3o", +"label.update.ipv4.subnet": "Atualizar sub-rede IPv4", "label.update.instance.group": "Grupo de inst\u00e2ncias de atualiza\u00e7\u00e3o", "label.update.ip.range": "Atualizar intervalo IP", "label.update.network": "Atualizar rede", "label.update.physical.network": "Atualizar rede f\u00edsica", "label.update.project.role": "Atualizar fun\u00e7\u00e3o do projeto", +"label.update.sharedfs": "Atualizar Sistema de Arquivos Compartilhado", "label.update.ssl": " atualizar certificado SSL", "label.update.to": "atualizado para", "label.update.traffic.label": "Atualizar etiquetas de tr\u00e1fego", "label.update.vmware.datacenter": "Atualizar VMware datacenter", +"label.update.webhook": "Atualizar Webhook", +"label.updateinsequence": "Atualizar em sequ\u00eancia", "label.updating": "Atualizando", "label.upgrade.router.newer.template": "Atualize roteador para usar template mais novo", +"label.upgrading": "Atualizando", "label.upload": "Enviar", +"label.upload.description": "Caminho de onde objetos serão enviados", +"label.upload.path": "Caminho de envio", "label.upload.icon": "Carregar \u00cdcone", "label.upload.iso.from.local": "Carregar ISO local", "label.upload.resource.icon": "Carregar \u00cdcone", @@ -1703,11 +2423,40 @@ "label.upload.volume.from.local": "Carregar volume local", "label.upload.volume.from.url": "Carregar volume por URL", "label.url": "URL", +"label.usage": "Uso", +"label.usage.explanation": "Nota: Apenas o Usage Server que possui o job de uso ativo \u00e9 mostrado aqui.", +"label.usage.records.downloading": "Baixando registros de uso", +"label.usage.records.fetch.child.domains": "Buscar registros de uso para dom\u00ednios filhos", +"label.usage.records.generate": "Gerar registros de uso", +"label.usage.records.generate.after": "Os registros de uso ser\u00e3o criados para o per\u00edodo ap\u00f3s ", +"label.usage.records.generate.description": "Se o job de uso agendado n\u00e3o foi executado ou falhou, isso gerar\u00e1 registros (apenas se houver registros a serem gerados)", +"label.usage.records.generated": "Um job foi criado para gerar registros de uso.", +"label.usage.records.purge": "Expurgar registros de uso", +"label.usage.records.purge.alert": "Expurgar registros de uso excluir\u00e1 permanentemente os registros do banco de dados. Dependendo dos dados sendo exclu\u00eddos, isso pode aumentar a carga no banco de dados e pode demorar um pouco. Tem certeza de que deseja continuar?", +"label.usage.records.purge.days": "Expurgar registros com mais de", +"label.usage.records.purge.days.description": "Expurgar registros com mais de o n\u00famero especificado de dias.", +"label.usage.records.usagetype.required": "O tipo de uso \u00e9 obrigat\u00f3rio com o ID do recurso", +"label.usageid": "ID do Recurso", +"label.usageislocal": "Um Usage Server est\u00e1 instalado localmente", +"label.usagename": "Tipo de uso", +"label.usagetypedescription": "Descri\u00e7\u00e3o do uso", "label.usageinterface": "Interface de uso", "label.usagetype": "Tipo", "label.usageunit": "Unidade", "label.use.kubectl.access.cluster": "os arquivos kubectl e kubeconfig para acessar o cluster", "label.use.local.timezone": "Use o fuso hor\u00e1rio local", +"label.userdata.do.append": "Anexar dados do usu\u00e1rio", +"label.userdata.do.override": "Substituir dados do usu\u00e1rio", +"label.userdata.registered": "Dados do usu\u00e1rio armazenados", +"label.userdata.text": "Entrada manual de dados do usu\u00e1rio", +"label.userdatadetails": "Detalhes dos dados do usu\u00e1rio", +"label.userdataid": "ID dos dados do usu\u00e1rio", +"label.userdataname": "Nome dos dados do usu\u00e1rio", +"label.userdataparams": "Par\u00e2metros dos dados do usu\u00e1rio", +"label.userdatapolicy": "Pol\u00edtica de link de dados do usu\u00e1rio", +"label.userdatapolicy.tooltip": "Os dados do usu\u00e1rio vinculados ao Template podem ser substitu\u00eddos pelos dados do usu\u00e1rio fornecidos durante a implanta\u00e7\u00e3o da Inst\u00e2ncia. Selecione a pol\u00edtica de substitui\u00e7\u00e3o conforme necess\u00e1rio.", +"label.username.tooltip": "O Nome de Usu\u00e1rio para o Host", +"label.keep.mac.address.on.public.nic": "Utilizar o mesmo endere\u00e7o MAC para a NIC p\u00fablica dos VRs", "label.used": "Utilizado", "label.usehttps": "Utilize HTTPS", "label.usenewdiskoffering": "Substituir a oferta de disco?", @@ -1732,7 +2481,9 @@ "label.vcenterpassword": "Senha vCenter", "label.vcenterusername": "Usu\u00e1rio vCenter", "label.vcsdeviceid": "ID", +"label.verify": "Verificar", "label.version": "Vers\u00e3o", +"label.versioning": "Versionamento", "label.versions": "Vers\u00f5es", "label.vgpu": "VGPU", "label.vgputype": "Tipo de vGPU", @@ -1740,11 +2491,14 @@ "label.view.all": "Visualizar tudo", "label.view.console": "Visualizar console", "label.viewing": "Visualizar", +"label.virtualmachine": "Inst\u00e2ncia", +"label.virtualmachinecount": "Contagem de Inst\u00e2ncias", "label.virtual.machine": "M\u00e1quina virtual", "label.virtual.machines": "M\u00e1quinas virtuais", "label.virtual.network": "Rede virtual", "label.virtual.networking": "Rede virtual", "label.virtual.routers": "Roteadores virtuais", +"label.virtual.routers.system.offering": "Oferta de sistema do roteador virtual", "label.virtualmachineid": "ID da VM", "label.virtualmachinename": "Nome da VM", "label.virtualsize": "Tamanho virtual", @@ -1766,18 +2520,59 @@ "label.vmlimit": "Limites de inst\u00e2ncias", "label.vmname": "Nome da VM", "label.vms": "VMs", +"label.vmscheduleactions": "A\u00e7\u00f5es", +"label.vmsnapshotlimit": "Limite de snapshots de VM", "label.vmstate": "Estado da VM", "label.vmtotal": "VMs totais", +"label.vmware": "VMware", "label.vmware.storage.policy": "Pol\u00edtica de armazenamento do VMWare", "label.vmwaredcid": "ID do datacenter VMware", "label.vmwaredcname": "Nome do datacenter VMware", "label.vmwaredcvcenter": "Vcednter do datacenter VMware", "label.vmwarenetworklabel": "Etiqueta de tr\u00e1fego VMware", -"label.vnf.appliance.add": "Adicionar VNF Appliance", "label.vnmc": "VNMC", +"label.vnf.app.action.destroy": "Destruir appliance VNF", +"label.vnf.app.action.edit": "Editar appliance VNF", +"label.vnf.app.action.expunge": "Expurgar appliance VNF", +"label.vnf.app.action.migrate.to.host": "Migrar appliance VNF para outro host", +"label.vnf.app.action.migrate.to.ps": "Migrar appliance VNF para outro armazenamento prim\u00e1rio", +"label.vnf.app.action.reboot": "Reiniciar appliance VNF", +"label.vnf.app.action.recover": "Recuperar appliance VNF", +"label.vnf.app.action.reinstall": "Reinstalar appliance VNF", +"label.vnf.app.action.scale": "Escalonar appliance VNF", +"label.vnf.app.action.start": "Iniciar appliance VNF", +"label.vnf.app.action.stop": "Parar appliance VNF", +"label.vnf.appliance": "Appliance VNF", +"label.vnf.appliance.add": "Adicionar Appliance VNF", +"label.vnf.appliance.access.methods": "Informa\u00e7\u00f5es de acesso de gerenciamento para este appliance VNF", +"label.vnf.appliances": "Appliances VNF", +"label.vnf.cidr.list": "CIDR a partir do qual o acesso \u00e0 interface de Gerenciamento do appliance VNF deve ser permitido", +"label.vnf.cidr.list.tooltip": "a lista CIDR para encaminhar tr\u00e1fego para a interface de gerenciamento VNF. M\u00faltiplas entradas devem ser separadas por um \u00fanico caractere de v\u00edrgula (,). O valor padr\u00e3o \u00e9 0.0.0.0/0.", +"label.vnf.configure.management": "Configurar regras de Firewall e Encaminhamento de Porta para as interfaces de gerenciamento da VNF", +"label.vnf.configure.management.tooltip": "Verdadeiro por padr\u00e3o, regras de grupo de seguran\u00e7a ou de rede (source nat e regras de firewall) ser\u00e3o configuradas para interfaces de gerenciamento VNF. Falso caso contr\u00e1rio. Saiba quais regras s\u00e3o configuradas em http://docs.cloudstack.apache.org/en/latest/adminguide/networking/vnf_templates_appliances.html#deploying-vnf-appliances", +"label.vnf.detail.add": "Adicionar detalhe VNF", +"label.vnf.detail.remove": "Remover detalhe VNF", +"label.vnf.details": "Detalhes VNF", +"label.vnf.nics": "NICs VNF", +"label.vnf.nic.add": "Adicionar NIC VNF", +"label.vnf.nic.delete": "Excluir NIC VNF", +"label.vnf.nic.description": "Descri\u00e7\u00e3o da NIC VNF", +"label.vnf.nic.deviceid": "ID do Dispositivo da NIC VNF. Come\u00e7a com 0. A NIC com deviceid como 0 para o appliance VNF ser\u00e1 a NIC padr\u00e3o.", +"label.vnf.nic.edit": "Editar NIC VNF", +"label.vnf.nic.management": "NIC de Gerenciamento", +"label.vnf.nic.management.description": "Verdadeiro se a NIC VNF for uma interface de gerenciamento. Falso caso contr\u00e1rio", +"label.vnf.nic.mappings": "Mapeamentos de NIC VNF", +"label.vnf.nic.name": "Nome da NIC VNF", +"label.vnf.nic.remove": "Remover NIC VNF", +"label.vnf.nic.required": "Verdadeiro se a NIC VNF for obrigat\u00f3ria. Caso contr\u00e1rio, opcional", +"label.vnf.settings": "Configura\u00e7\u00f5es VNF", +"label.vnf.template.register": "Registrar template VNF", +"label.vnf.templates": "Templates VNF", "label.volgroup": "Grupo de volume", "label.volume": "Disco", "label.volume.empty": "Nenhum volume de dados anexado a esta VM", +"label.volume.encryption.support": "Criptografia de Volume Suportada", +"label.volume.metrics": "M\u00e9tricas de Volume", "label.volume.volumefileupload.description": "Clique ou arraste o arquivo para esta \u00e1rea para carreg\u00e1-lo", "label.volumechecksum": "MD5 checksum", "label.volumechecksum.description": "Utilize o hash que voc\u00ea criou no inicio do procedimento de carregamento de volume", @@ -1789,15 +2584,18 @@ "label.volumename": "Nome do disco", "label.volumes": "Discos", "label.volumetotal": "Disco", +"label.volumetype": "Tipo de Volume", "label.vpc": "VPC", "label.vpc.id": "ID da VPC", "label.vpc.offerings": "Ofertas VPC", "label.vpc.virtual.router": "Roteador virtual VPC", +"label.vpc.restart.required": "Reinicializa\u00e7\u00e3o de VPC necess\u00e1ria", "label.vpcid": "VPC", "label.vpclimit": "Limites VPC", "label.vpcname": "VPC", "label.vpcoffering": "Oferta VPC", "label.vpcofferingid": "Oferta VPC", +"label.vpcs": "VPCs", "label.vpn": "VPN", "label.vpn.connection": "Conex\u00e3o VPN", "label.vpn.gateway": "Gateway de VPN", @@ -1818,20 +2616,26 @@ "label.warn": "Avisar", "label.warn.upper": "AVISO", "label.warning": "Aten\u00e7\u00e3o", +"label.webhook": "Webhook", +"label.webhook.deliveries": "Entregas de Webhook", +"label.webhookname": "Webhook", +"label.webhooks": "Webhooks", "label.wednesday": "Quarta-Feira", "label.weekly": "Semanal", "label.welcome": "Bem-Vindo", "label.what.is.cloudstack": "O que \u00e9 o CloudStack™?", "label.windows": "Windows", "label.with.snapshotid": "com o ID da snapshot", -"label.write": "Escreva", +"label.write": "Escrita", "label.writeback": "Write-back", "label.writecachetype": "Tipo do cache de escrita", "label.writeio": "Escrita (IO)", "label.writethrough": "Write-through", "label.xennetworklabel": "Etiqueta de tr\u00e1fego XenServer", +"label.xenserver": "XenServer", "label.xenservertoolsversion61plus": "Vers\u00e3o original do XS \u00e9 6.1+", "label.yes": "Sim", +"label.your.autoscale.vmgroup": "Seu grupo de escalonamento autom\u00e1tico", "label.yourinstance": "Sua inst\u00e2ncia", "label.zone": "Zona", "label.zone.dedicated": "Zona dedicada", @@ -1844,18 +2648,37 @@ "label.zonenamelabel": "Nome da zona", "label.zones": "Zonas", "label.zonewizard.traffictype.storage": "Armazenamento: tr\u00e1fego entre servidores de armazenamento prim\u00e1ria e secund\u00e1ria, tais como templates de m\u00e1quinas virtuais e snapshots", +"label.quotagb": "Cota em GB", +"label.quotagib": "Cota em GiB", "message.acquire.ip.failed": "Falha ao adquirir IP", +"message.action.about.mandate.and.disable.2FA.user.auth": "A autentica\u00e7\u00e3o de dois fatores \u00e9 obrigat\u00f3ria para o usu\u00e1rio. Se for desativada agora, o usu\u00e1rio precisar\u00e1 configurar a autentica\u00e7\u00e3o de dois fatores novamente no pr\u00f3ximo login.

Por favor, confirme que voc\u00ea deseja desativ\u00e1-la.", "message.action.acquire.ip": "Por favor, confirme que voc\u00ea quer adquirir um novo IP", "message.action.cancel.maintenance": "A manuten\u00e7\u00e3o do seu host foi cancelada com sucesso", "message.action.cancel.maintenance.mode": "Confirme que voc\u00ea deseja cancelar esta manuten\u00e7\u00e3o", "message.action.create.snapshot.from.vmsnapshot": "Por favor, confirme que voc\u00ea quer criar uma snapshot a partir de uma snapshot de VM", +"message.action.delete.asnrange": "Por favor, confirme a faixa AS que voc\u00ea deseja excluir", +"message.action.delete.autoscale.vmgroup": "Por favor, confirme que voc\u00ea deseja excluir este grupo de autoescalonamento.", "message.action.delete.backup.offering": "Por favor, confirme que voc\u00ea quer remover esta oferta de backup", +"message.action.delete.backup.repository": "Por favor, confirme que voc\u00ea deseja excluir este reposit\u00f3rio de backup?", +"message.action.delete.backup.schedule": "Confirme que voc\u00ea deseja remover esse agendamento de backup.", "message.action.delete.cluster": "Confirme que voc\u00ea deseja excluir este host", +"message.action.delete.domain": "Por favor, confirme que voc\u00ea deseja excluir este dom\u00ednio.", "message.action.delete.disk.offering": "Confirme que voc\u00ea deseja excluir esta oferta de disco", +"message.action.delete.external.firewall": "Por favor, confirme que voc\u00ea gostaria de remover este firewall externo. Aviso: Se voc\u00ea planeja adicionar de volta o mesmo firewall externo, deve redefinir os dados de uso no dispositivo.", +"message.action.delete.external.load.balancer": "Por favor, confirme que voc\u00ea gostaria de remover este balanceador de carga externo. Aviso: Se voc\u00ea planeja adicionar de volta o mesmo balanceador de carga externo, deve redefinir os dados de uso no dispositivo.", +"message.action.delete.gui.theme": "Por favor, confirme que voc\u00ea deseja excluir este tema da GUI", +"message.action.delete.guest.os": "Por favor, confirme que voc\u00ea deseja excluir este SO convidado. Entradas definidas pelo sistema n\u00e3o podem ser exclu\u00eddas.", +"message.action.delete.guest.os.hypervisor.mapping": "Por favor, confirme que voc\u00ea deseja excluir este mapeamento de hypervisor do SO convidado. Entradas definidas pelo sistema n\u00e3o podem ser exclu\u00eddas.", +"message.action.delete.ingress.rule": "Por favor, confirme que voc\u00ea deseja excluir esta regra de ingresso.", "message.action.delete.instance.group": "Por favor, confirme que voc\u00ea quer remover o grupo de inst\u00e2ncia", +"message.action.delete.interface.static.route": "Por favor, confirme que voc\u00ea deseja remover esta Rota Est\u00e1tica de interface?", "message.action.delete.iso": "Confirme que voc\u00ea deseja excluir esta ISO", +"message.action.delete.ipv4.subnet": "Por favor, confirme que voc\u00ea deseja excluir esta sub-rede IPv4.", +"message.action.delete.network.static.route": "Por favor, confirme que voc\u00ea deseja remover esta Rota Est\u00e1tica de Rede", "message.action.delete.network": "Confirme que voc\u00ea deseja remover esta rede.", +"message.action.delete.nexusvswitch": "Por favor, confirme que voc\u00ea deseja excluir este nexus 1000v", "message.action.delete.node": "Por favor, confirme que voc\u00ea quer remover este nodo.", +"message.action.delete.oauth.provider": "Por favor, confirme que voc\u00ea deseja excluir o provedor OAuth.", "message.action.delete.physical.network": "Por favor confirme que voc\u00ea deseja deletar esta rede f\u00edsica", "message.action.delete.pod": "Confirme que voc\u00ea deseja remover este pod.", "message.action.delete.secondary.storage": "Confirme que voc\u00ea deseja remover este armazenamento secund\u00e1rio.", @@ -1864,30 +2687,46 @@ "message.action.delete.snapshot": "Confirme que voc\u00ea deseja remover esta snapshot.", "message.action.delete.system.service.offering": "Por favor confirme que voc\u00ea deseja deletar esta oferta de servi\u00e7o de sistema.", "message.action.delete.template": "Confirme que voc\u00ea deseja remover este template.", +"message.action.delete.theme": "Por favor, confirme que voc\u00ea deseja excluir este tema.", +"message.action.delete.tungsten.router.table": "Por favor, confirme que voc\u00ea deseja remover a Tabela de Rotas desta Rede?", "message.action.delete.volume": "Confirme que voc\u00ea deseja remover este disco.", "message.action.delete.vpn.user": "Por favor, confirme que voc\u00ea quer remover o usu\u00e1rio da VPN.", "message.action.delete.zone": "Confirme que voc\u00ea deseja remover esta zona.", "message.action.destroy.instance": "Por favor, confirme que voc\u00ea deseja excluir esta inst\u00e2ncia.", "message.action.destroy.instance.with.backups": "Por favor, confirme que voc\u00ea quer destruir a inst\u00e2ncia. \u00c9 poss\u00edvel que haja backups associados a inst\u00e2ncia no qual n\u00e3o ser\u00e3o removidos.", +"message.action.destroy.sharedfs": "Por favor, confirme que voc\u00ea deseja destruir este Sistema de Arquivos Compartilhado.
Cuidado: Isso excluir\u00e1 todos os dados do Sistema de Arquivos Compartilhado tamb\u00e9m.", "message.action.destroy.systemvm": "Confirme que voc\u00ea deseja excluir esta VM de sistema.", "message.action.destroy.volume": "Por favor, confirme que voc\u00ea quer destruir este volume.", +"message.action.disable.2FA.user.auth": "Por favor, confirme que voc\u00ea deseja desativar a autentica\u00e7\u00e3o de dois fatores do usu\u00e1rio.", "message.action.disable.cluster": "Confirma a desativa\u00e7\u00e3o do cluster.", +"message.action.disable.disk.offering": "Por favor, confirme que voc\u00ea deseja desativar esta oferta de disco.", "message.action.disable.physical.network": "Por favor confirme que voc\u00ea deseja desabilitar esta rede f\u00edsica.", "message.action.disable.pod": "Confirma a desativa\u00e7\u00e3o do pod.", "message.action.disable.static.nat": "Confirme que voc\u00ea deseja desativar o NAT est\u00e1tico.", +"message.action.disable.service.offering": "Por favor, confirme que voc\u00ea deseja desativar esta oferta de servi\u00e7o.", +"message.action.disable.system.service.offering": "Por favor, confirme que voc\u00ea deseja desativar esta oferta de servi\u00e7o do sistema.", "message.action.disable.zone": "Confirma a desativa\u00e7\u00e3o da zona.", "message.action.download.iso": "Por favor confirme que voc\u00ea deseja baixar esta ISO.", "message.action.download.snapshot": "Por favor confirme que voc\u00ea deseja baixar esta snapshot.", "message.action.download.template": "Por favor confirme que voc\u00ea deseja baixar este template.", +"message.action.edit.nfs.mount.options": "Altera\u00e7\u00f5es nas op\u00e7\u00f5es de montagem NFS s\u00f3 ter\u00e3o efeito ao cancelar o modo de manuten\u00e7\u00e3o, o que far\u00e1 com que o pool de armazenamento seja remontado em todos os hosts KVM com as novas op\u00e7\u00f5es de montagem.", "message.action.enable.cluster": "Confirma a ativa\u00e7\u00e3o do cluster.", +"message.action.enable.disk.offering": "Por favor, confirme que voc\u00ea deseja ativar esta oferta de disco.", +"message.action.enable.service.offering": "Por favor, confirme que voc\u00ea deseja ativar esta oferta de servi\u00e7o.", +"message.action.enable.system.service.offering": "Por favor, confirme que voc\u00ea deseja ativar esta oferta de servi\u00e7o do sistema.", "message.action.enable.physical.network": "Por favor confirme que voc\u00ea deseja habilitar esta rede f\u00edsica.", "message.action.enable.pod": "Confirma a ativa\u00e7\u00e3o do pod.", "message.action.enable.zone": "Confirma a ativa\u00e7\u00e3o da zona.", "message.action.expunge.instance": "Por favor, confirme que voc\u00ea deseja eliminar esta inst\u00e2ncia.", "message.action.expunge.instance.with.backups": "Por favor, confirme que voc\u00ea quer eliminar esta inst\u00e2ncia. \u00c9 poss\u00edvel que haja backups associados a inst\u00e2ncia no qual n\u00e3o ser\u00e3o removidos.", +"message.action.expunge.sharedfs": "Por favor, confirme que voc\u00ea deseja expurgar este Sistema de Arquivos Compartilhado.", "message.action.host.enable.maintenance.mode": "Ativar o modo de manuten\u00e7\u00e3o ir\u00e1 causar o live migration de todas as inst\u00e2ncias hospedadas neste host para o pr\u00f3ximo dispon\u00edvel.", "message.action.instance.reset.password": "Por favor confirme que voc\u00ea deseja alterar a senha de root para est\u00e1 m\u00e1quina virtual.", "message.action.manage.cluster": "Confirma a vincula\u00e7\u00e3o do cluster.", +"message.action.patch.router": "Por favor, confirme que voc\u00ea deseja aplicar um patch ao vivo no roteador.
Esta opera\u00e7\u00e3o \u00e9 equivalente a atualizar os pacotes do roteador e reiniciar a Rede sem limpeza.", +"message.action.patch.systemvm": "Por favor, confirme que voc\u00ea deseja aplicar um patch na VM de Sistema.", +"message.action.primary.storage.scope.cluster": "Por favor, confirme que voc\u00ea deseja alterar o escopo de zona para o cluster especificado.
Esta opera\u00e7\u00e3o atualizar\u00e1 o banco de dados e desconectar\u00e1 o pool de armazenamento de todos os hosts que estavam conectados anteriormente ao armazenamento prim\u00e1rio e n\u00e3o fazem parte do cluster especificado.", +"message.action.primary.storage.scope.zone": "Por favor, confirme que voc\u00ea deseja alterar o escopo de cluster para zona.
Esta opera\u00e7\u00e3o atualizar\u00e1 o banco de dados e conectar\u00e1 o pool de armazenamento a todos os hosts da zona executando o mesmo hypervisor definido no pool de armazenamento.", "message.action.primarystorage.enable.maintenance.mode": "Aviso: colocar o armazenamento prim\u00e1rio em modo de manuten\u00e7\u00e3o ir\u00e1 causar a parada de todas as VMs hospedadas nesta unidade. Deseja continuar?", "message.action.quota.tariff.create.error.namerequired": "Por favor, informe o nome da tarifa.", "message.action.quota.tariff.create.error.usagetyperequired": "Por favor, selecione o tipo da tarifa.", @@ -1896,8 +2735,16 @@ "message.action.reboot.instance": "Por favor, confirme que voc\u00ea deseja reiniciar esta inst\u00e2ncia.", "message.action.reboot.router": "Confirme que voc\ufffd deseja reiniciar este roteador.", "message.action.reboot.systemvm": "Confirme que voc\u00ea deseja reiniciar esta VM de sistema.", +"message.action.recover.sharedfs": "Por favor, confirme que voc\u00ea gostaria de recuperar este Sistema de Arquivos Compartilhado.", "message.action.recover.volume": "Por favor, confirme que voc\u00ea quer recuperar este volume.", +"message.action.release.asnumber": "Por favor, confirme que voc\u00ea deseja liberar este N\u00famero AS.", "message.action.release.ip": "Confirme que voc\u00ea deseja liberar este IP.", +"message.action.release.reserved.ip": "Por favor, confirme que voc\u00ea deseja liberar este IP reservado.", +"message.action.remove.host": "Por favor, confirme que voc\u00ea deseja remover este host.", +"message.action.remove.logical.router": "Por favor, confirme que voc\u00ea deseja remover o Roteador L\u00f3gico?", +"message.action.remove.routing.policy": "Por favor, confirme que voc\u00ea deseja remover a Pol\u00edtica de Roteamento desta Rede", +"message.action.reserve.ip": "Por favor, confirme que voc\u00ea deseja reservar este IP.", +"message.action.restart.sharedfs": "Por favor, confirme que voc\u00ea deseja reiniciar este Sistema de Arquivos Compartilhado. Isso causar\u00e1 um tempo de inatividade para o usu\u00e1rio.
Reiniciar com limpeza reinicializar\u00e1 a Inst\u00e2ncia do Sistema de Arquivos Compartilhado sem afetar o sistema de arquivos instalado.", "message.action.revert.snapshot": "Por favor, confirme que voc\u00ea deseja reverter o seu volume desta snapshot.", "message.action.router.health.checks": "Resultados das checagens de sa\u00fade ser\u00e3o obtidos do roteador.", "message.action.router.health.checks.disabled.warning": "Por favor, habilite as checagens de sa\u00fade do roteador.", @@ -1908,15 +2755,20 @@ "message.action.settings.warning.vm.running": "Por favor, pare a VM para acessar as configura\u00e7\u00f5es", "message.action.start.instance": "Por favor, confirme que voc\u00ea deseja iniciar esta inst\u00e2ncia.", "message.action.start.router": "Confirme que voc\u00ea deseja inciar este roteador.", +"message.action.start.sharedfs": "Por favor, confirme que voc\u00ea deseja iniciar este Sistema de Arquivos Compartilhado.", "message.action.start.systemvm": "Confirme que voc\u00ea deseja iniciar esta VM de sistema.", "message.action.stop.instance": "Por favor, confirme que voc\u00ea deseja parar esta inst\u00e2ncia.", "message.action.stop.router": "Confirme que voc\ufffd deseja parar este roteador.", +"message.action.stop.sharedfs": "Por favor, confirme que voc\u00ea deseja parar este Sistema de Arquivos Compartilhado.", "message.action.stop.systemvm": "Confirme que voc\u00ea deseja parar esta VM de sistema.", "message.action.unmanage.cluster": "Confirma a desvincula\u00e7\u00e3o do cluster.", "message.action.unmanage.instance": "Por favor, confirme que voc\u00ea deseja parar de gerenciar a inst\u00e2ncia.", "message.action.unmanage.instances": "Por favor, confirme que voc\u00ea deseja parar de gerenciar as inst\u00e2ncias.", "message.action.unmanage.virtualmachine": "Por favor, confirme que voc\u00ea deseja parar de gerenciar a VM.", -"message.action.vmsnapshot.delete": "Por favor, confirme que voc\u00ea deseja excluir esta snapshot de VM.", +"message.action.unmanage.volume": "Por favor, confirme que voc\u00ea deseja parar de gerenciar o Volume.", +"message.action.unmanage.volumes": "Por favor, confirme que voc\u00ea deseja parar de gerenciar os Volumes.", +"message.action.vmsnapshot.delete": "Por favor, confirme que voc\u00ea deseja excluir esta snapshot de VM.
Saiba que caso a instância execute em um hypervisor KVM, ela será pausada antes da deleç\u00e3o, e continuada após a deleç\u00e3o.", +"message.action.vmsnapshot.disk-only.delete": "Por favor, confirme que voc\u00ea deseja excluir esta snapshot de VM.", "message.activate.project": "Voc\u00ea tem certeza que deseja ativar este projeto?", "message.add.egress.rule.failed": "Falha ao adicionar uma nova regra de sa\u00edda", "message.add.egress.rule.processing": "Adicionando uma nova regra de sa\u00edda...", @@ -1924,14 +2776,29 @@ "message.add.firewall": "Adicionar firewall \u00e0\u00a0 zona.", "message.add.firewall.rule.failed": "Falha ao adicionar uma nova regra de firewall", "message.add.firewall.rule.processing": "Adicionando uma nova regra de firewall...", +"message.add.firewallrule.failed": "Falha ao adicionar Regra de Firewall", "message.add.host": "Especifique os seguintes par\u00e2metros para adicionar um novo host.", +"message.add.host.sshkey": "AVISO: Para adicionar um host com chave SSH, voc\u00ea deve garantir que o host do seu hypervisor foi configurado corretamente.", "message.add.iprange.processing": "Adicionando intervalo IP...", +"message.add.ipv4.subnet.for.guest.network.failed": "Falha ao adicionar sub-rede IPv4 para rede guest", +"message.add.ipv4.subnet.for.guest.network.processing": "Adicionando sub-rede IPv4 para rede guest...", +"message.add.ip.v6.prefix.processing": "Adicionando Prefixo IPv6...", +"message.add.ip.v6.firewall.rule.failed": "Falha ao adicionar regra de firewall IPv6", +"message.add.ip.v6.firewall.rule.processing": "Adicionando regra de firewall IPv6...", +"message.add.ip.v6.firewall.rule.success": "Regra de firewall IPv6 adicionada", +"message.redeliver.webhook.delivery": "Reentregar esta entrega de Webhook", +"message.remove.ip.v6.firewall.rule.failed": "Falha ao remover regra de firewall IPv6", +"message.remove.ip.v6.firewall.rule.processing": "Removendo regra de firewall IPv6...", +"message.remove.ip.v6.firewall.rule.success": "Regra de firewall IPv6 removida", +"message.add.nsx.controller": "Adicionar Provedor NSX", "message.add.network": "Adicionar uma nova rede para a zona: ", "message.add.network.acl.failed": "Falha ao adicionar lista de rede ACL", "message.add.network.acl.processing": "Adicionando lista de rede ACL...", "message.add.network.failed": "Falha ao adicionar rede", "message.add.network.processing": "Adicionando rede...", "message.add.new.gateway.to.vpc": "Favor especificar a informa\u00e7\u00e3o para adicionar um novo gateway a esta VPC.", +"message.add.physical.network.failed": "Falha ao adicionar rede f\u00edsica", +"message.add.physical.network.processing": "Adicionando uma nova rede f\u00edsica...", "message.add.pod": "Adicionar um novo pod para a zona ", "message.add.pod.during.zone.creation": "Cada zona deve conter um ou mais pods e iremos adicionar o primeiro pod agora. Um pod cont\u00e9m hosts e servidores de armazenamento prim\u00e1rio que ser\u00e3o adicionados em uma etapa posterior. Inicialmente, configure um intervalo de endere\u00e7os IP reservados para o tr\u00e1fego de gerenciamento interno do CloudStack. A faixa de IP reservados devem ser \u00fanicos para cada zona na nuvem.", "message.add.port.forward.failed": "Falha ao adicionar nova regra de encaminhamento de porta", @@ -1940,6 +2807,9 @@ "message.add.private.gateway.processing": "Adicionando gateway privado...", "message.add.resource.description": "Adicionar recursos de infraestrutura", "message.add.resource.hint": "Adicionar recursos de infraestrutura - pods, clusters, armazenamentos prim\u00e1rios/secund\u00e1rios.", +"message.add.routing.firewall.rule.failed": "Falha ao adicionar regra de firewall de Roteamento IPv4", +"message.add.routing.firewall.rule.processing": "Adicionando regra de firewall de Roteamento IPv4...", +"message.add.routing.firewall.rule.success": "Regra de firewall de Roteamento IPv4 adicionada", "message.add.rule.failed": "Falha ao adicionar nova regra", "message.add.rule.processing": "Adicionando nova regra do security-group...", "message.add.secondary.ipaddress.processing": "Adicionando endere\u00e7o IP secund\u00e1rio...", @@ -1948,7 +2818,10 @@ "message.add.tag.failed": "Falha ao adicionar nova etiqueta", "message.add.tag.for.networkacl": "Adicionar etiqueta para rede ACL", "message.add.tag.processing": "Adicionando nova etiqueta...", +"message.add.template": "Por favor, insira os seguintes dados para criar o seu novo Template", +"message.add.tungsten.routing.policy.available": "A pol\u00edtica de roteamento Tungsten-Fabric est\u00e1 pronta para iniciar. Por favor, prossiga para a pr\u00f3xima etapa.", "message.add.user.to.project": "Este formul\u00e1rio permitir\u00e1 adicionar usu\u00e1rios espec\u00edficos de uma conta a um projeto.
Al\u00e9m disso, uma fun\u00e7\u00e3o de projeto pode ser adicionada ao usu\u00e1rio/conta para permitir/n\u00e3o permitir o acesso API em n\u00edvel de projeto.
Tamb\u00e9m podemos especificar a fun\u00e7\u00e3o com a qual o usu\u00e1rio deve ser adicionado a um projeto - admin/Regular; se n\u00e3o for especificado, o padr\u00e3o \u00e9 'Regular'.", +"message.add.volume": "Por favor, preencha os seguintes dados para adicionar um novo volume.", "message.add.vpn.connection.failed": "Falha ao adicionar conex\u00e3o VPN", "message.add.vpn.connection.processing": "Adicionando conex\u00e3o VPN...", "message.add.vpn.customer.gateway": "Adicionando o gateway da VPN do cliente", @@ -1956,11 +2829,14 @@ "message.add.vpn.gateway": "Favor confirmar que voc\u00ea deseja adicionar um gateway de VPN", "message.add.vpn.gateway.failed": "Falha ao adicionar gateway da VPN", "message.add.vpn.gateway.processing": "Adicionando gateway da VPN...", +"message.added.vpc.offering": "Oferta de VPC adicionada", +"message.adding.firewall.policy": "Adicionando Pol\u00edtica de Firewall", "message.adding.host": "Adicionando host", "message.adding.netscaler.device": "Adicionando dispositivo Nescaler", "message.adding.netscaler.provider": "Adicionando Netscaler provider", "message.advanced.security.group": "Escolha esta op\u00e7\u00e3o se desejar utilizar grupos de seguran\u00e7a para isolamento das VMs guest.", "message.alert.show.all.stats.data": "Isso pode retornar muitos dados dependendo das configura\u00e7\u00f5es de coleta e reten\u00e7\u00e3o de estat\u00edsticas.", +"message.allowed": "Permitido", "message.apply.success": "Aplicado com sucesso", "message.assign.instance.another": "Favor especificar o tipo de conta, dom\u00ednio, nome da conta e rede (opcional) da nova conta.
Se o NIC padr\u00e3o da VM estiver em uma rede compartilhada, o CloudStack verificar\u00e1 se a rede pode ser usada pela nova conta se voc\u00ea n\u00e3o especificar uma rede.
Se o NIC padr\u00e3o da VM estiver em uma rede isolada, e a nova conta tiver mais uma rede isolada, voc\u00ea deve especificar uma.", "message.assign.vm.failed": "Falha na designa\u00e7\u00e3o de VM", @@ -1968,26 +2844,52 @@ "message.attach.volume": "Preencha os seguintes dados para conectar o novo disco. Se voc\u00ea est\u00e1 conectando um disco a uma m\u00e1quina virtual Windows, ser\u00e1 necess\u00e1rio reiniciar a inst\u00e2ncia para visualizar o novo disco.", "message.attach.volume.failed": "Falha ao anexar volume", "message.attach.volume.progress": "Anexando volume", +"message.attach.volume.success": "Volume anexado com sucesso \u00e0 inst\u00e2ncia", "message.authorization.failed": "Sess\u00e3o expirada, a verifica\u00e7\u00e3o de autoriza\u00e7\u00e3o falhou", +"message.autoscale.loadbalancer.update": "A regra do balanceador de carga s\u00f3 pode ser atualizada quando o grupo de autoescalonamento estiver DESATIVADO.", +"message.autoscale.policies.update": "As pol\u00edticas de aumento/diminui\u00e7\u00e3o s\u00f3 podem ser atualizadas quando o grupo de autoescalonamento estiver DESATIVADO.", +"message.autoscale.vm.networks": "Por favor, escolha pelo menos uma Rede para as Inst\u00e2ncias no grupo de autoescalonamento. A Rede padr\u00e3o deve ser uma Rede Isolada ou Camada de Rede VPC que suporte AutoScale de Inst\u00e2ncia e tenha regras de balanceamento de carga.", +"message.autoscale.vmprofile.update": "O perfil de Inst\u00e2ncia de AutoScale s\u00f3 pode ser atualizado quando o grupo de autoescalonamento estiver DESATIVADO.", "message.backup.attach.restore": "Favor confirmar que voc\u00ea deseja restaurar e anexar o volume do backup?", "message.backup.create": "Voc\u00ea tem certeza de que quer criar um backup da VM?", "message.backup.offering.remove": "Voc\u00ea tem certeza que quer remover a VM da oferta de backup e excluir a cadeia de backup?", "message.backup.restore": "Por favor, confirme que voc\u00ea deseja restaurar o backup da VM?", +"message.bgp.peers.null": "Por favor note, se nenhum par BGP for selecionado, o VR conectar\u00e1 a
(1) pares BGP dedicados que o propriet\u00e1rio pode acessar, se o propriet\u00e1rio tiver pares BGP dedicados e a configura\u00e7\u00e3o da conta use.system.bgp.peers estiver definida como false;
(2) todos os pares BGP que o propriet\u00e1rio pode acessar, caso contr\u00e1rio.
", +"message.bucket.delete": "Por favor, confirme que voc\u00ea deseja excluir este Bucket", +"message.cancel.shutdown": "Por favor, confirme que voc\u00ea gostaria de cancelar o desligamento neste Management Server. Ele voltar\u00e1 a aceitar quaisquer novos job Ass\u00edncronos.", "message.certificate.upload.processing": "Carregamento de certificados em andamento", +"message.change.disk.offering.sharedfs.failed": "Falha ao alterar oferta de disco para o Sistema de Arquivos Compartilhado.", +"message.change.disk.offering.sharedfs.processing": "Alterando oferta de disco para o Sistema de Arquivos Compartilhado.", "message.change.offering.confirm": "Por favor, confirme que voc\u00ea deseja mudar a oferta de servi\u00e7o desta inst\u00e2ncia virtual.", +"message.change.offering.for.volume": "Por favor, confirme que voc\u00ea deseja alterar a oferta de disco para o volume", +"message.change.offering.for.volume.failed": "Falha ao alterar oferta para o volume", +"message.change.offering.processing": "Alterando oferta para o volume...", "message.change.password": "Por favor, troque sua senha.", +"message.change.scope.failed": "Falha na altera\u00e7\u00e3o de escopo", +"message.change.scope.processing": "Altera\u00e7\u00e3o de escopo em andamento", +"message.change.service.offering.sharedfs.failed": "Falha ao alterar oferta de servi\u00e7o para o Sistema de Arquivos Compartilhado.", +"message.change.service.offering.sharedfs.processing": "Alterando oferta de servi\u00e7o para o Sistema de Arquivos Compartilhado.", "message.cluster.dedicated": "Cluster dedicado", "message.cluster.dedication.released": "Cluster dedicado liberado", +"message.config.health.monitor.failed": "Configura\u00e7\u00e3o do Monitor de Sa\u00fade falhou", "message.config.sticky.policy.failed": "Falha na configura\u00e7\u00e3o da pol\u00edtica sticky", "message.config.sticky.policy.processing": "Atualizando pol\u00edtica sticky...", +"message.configure.network.ip.and.mac": "Por favor, configure o endere\u00e7o IP e o endere\u00e7o mac das redes, se necess\u00e1rio.", +"message.configure.network.select.default.network": "Por favor, configure o endere\u00e7o IP e o endere\u00e7o mac das redes, se necess\u00e1rio. Por favor, selecione uma rede como a rede padr\u00e3o.", "message.configuring.guest.traffic": "Configurando tr\u00e1fego do guest", +"message.configuring.nsx.public.traffic": "Configurando tr\u00e1fego p\u00fablico NSX", "message.configuring.physical.networks": "Configurando redes f\u00edsicas", "message.configuring.public.traffic": "Configurando tr\u00e1fego p\u00fablico", "message.configuring.storage.traffic": "Configurando tr\u00e1fego de storage", "message.confirm.action.force.reconnect": "Por favor confirme que voc\u00ea deseja for\u00e7ar a reconex\u00e3o com este host.", +"message.confirm.add.router.table.to.instance": "Por favor, confirme que voc\u00ea deseja adicionar a Tabela de Rotas a esta NIC", +"message.confirm.add.routing.policy": "Por favor, confirme que voc\u00ea deseja adicionar a Pol\u00edtica de Roteamento a esta Rede", "message.confirm.archive.selected.alerts": "Por favor confirme que voc\u00ea deseja arquivar os alertas selecionados", "message.confirm.archive.selected.events": "Por favor confirme que voc\u00ea deseja arquivar os eventos selecionados", "message.confirm.attach.disk": "Voc\u00ea tem certeza que deseja conectar este disco?", +"message.confirm.change.disk.offering.for.sharedfs": "Por favor, confirme que voc\u00ea deseja alterar a oferta de disco para o Sistema de Arquivos Compartilhado. Isso pode migrar o volume subjacente para um pool de armazenamento diferente, se necess\u00e1rio.", +"message.confirm.change.offering.for.volume": "Por favor, confirme que voc\u00ea deseja alterar a oferta de disco para o volume", +"message.confirm.change.service.offering.for.sharedfs": "Por favor, confirme que voc\u00ea deseja alterar a oferta de servi\u00e7o para o Sistema de Arquivos Compartilhado.", "message.confirm.configure.ovs": "Voc\u00ea tem certeza de que quer configurar os Ovs?", "message.confirm.delete.acl": "Voc\u00ea tem certeza que deseja apagar esta lista ACL?", "message.confirm.delete.bigswitchbcf": "Por favor, confirme que voc\u00ea deseja deletar este controlador BigSwitch BCF", @@ -2000,42 +2902,66 @@ "message.confirm.delete.niciranvp": "Por favor, confirme que voc\u00ea deseja excluir o controlador Nicira Nvp", "message.confirm.delete.pa": "Por favor, confirme que voc\u00ea deseja remover Palo Alto", "message.confirm.delete.provider": "Por favor, confirme que voc\u00ea deseja excluir este provedor?", +"message.confirm.delete.traffic.type": "Por favor, confirme que voc\u00ea gostaria de excluir o tipo de tr\u00e1fego.", "message.confirm.delete.srx": "Por favor confirme que voc\u00ea deseja remover o SRX", "message.confirm.destroy.router": "Por favor confirme que voc\u00ea gostaria de destruir este roteador", +"message.confirm.disable.autoscale.vmgroup": "Por favor, confirme que voc\u00ea deseja desativar este grupo de autoescalonamento.", "message.confirm.disable.host": "Favor confirmar que voc\u00ea deseja desabilitar este host.", "message.confirm.disable.network.offering": "Voc\u00ea tem certeza que deseja deshabilitar esta oferta de rede?", "message.confirm.disable.provider": "Por favor confirme que voc\u00ea gostaria de desabilitar este provider", "message.confirm.disable.storage": "Por favor, confirme que voc\u00ea deseja desabilitar o pool de armazenamento", "message.confirm.disable.vpc.offering": "Voc\u00ea tem certeza que deseja desabilitar esta oferta de VPC?", +"message.confirm.disable.webhook": "Por favor, confirme que voc\u00ea deseja desativar este webhook.", +"message.confirm.enable.autoscale.vmgroup": "Por favor, confirme que voc\u00ea deseja ativar este grupo de autoescalonamento.", "message.confirm.enable.host": "Por favor confirme que voc\u00ea deseja habilitar este host.", "message.confirm.enable.network.offering": "Voc\u00ea tem certeza que deseja habilitar esta oferta de rede?", "message.confirm.enable.provider": "Por favor confirme que voc\u00ea gostaria de habilitar este provider", "message.confirm.enable.storage": "Por favor, confirme que voc\u00ea deseja hablitar o pool de armazenamento", "message.confirm.enable.vpc.offering": "Voc\u00ea tem certeza que deseja habilitar esta oferta de VPC?", +"message.confirm.enable.webhook": "Por favor, confirme que voc\u00ea deseja ativar este webhook.", +"message.confirm.remove.firewall.rule": "Por favor, confirme que voc\u00ea deseja excluir esta Regra de Firewall?", "message.confirm.remove.ip.range": "Por favor confirme que voc\u00ea deseja remover este range de IP.", "message.confirm.remove.network.offering": "Voc\u00ea tem certeza que deseja remover esta oferta de rede?", +"message.confirm.remove.network.policy": "Por favor, confirme que voc\u00ea deseja remover esta Pol\u00edtica de Rede?", +"message.confirm.remove.routing.policy": "Por favor, confirme que voc\u00ea deseja excluir esta Pol\u00edtica de Roteamento?", "message.confirm.remove.selected.alerts": "Por favor confirme que voc\u00ea deseja remover os alertas selecionados", "message.confirm.remove.selected.events": "Por favor confirme que voc\u00ea deseja remover os eventos selecionados", "message.confirm.remove.vmware.datacenter": "Por favor, confirme que voc\u00ea quer remover este VMware datacenter", "message.confirm.remove.vpc.offering": "Voc\u00ea tem certeza que deseja remover esta oferta de VPC?", "message.confirm.replace.acl.new.one": "Voc\u00ea deseja substituir a ACL com uma nova?", +"message.confirm.reset.network.permissions": "Tem certeza de que deseja redefinir as permiss\u00f5es desta Rede?", "message.confirm.scale.up.router.vm": "Voc\u00ea realmente quer escalonar a VM do roteador?", "message.confirm.scale.up.system.vm": "Voc\u00ea realmente quer escalonar a VM do sistema?", "message.confirm.start.lb.vm": "Confirme que voc\u00ea deseja iniciar esta LB VM", "message.confirm.sync.storage": "Por favor, confirme que voc\u00ea gostaria de sincronizar o pool de armazenamento", +"message.confirm.type": "Para confirmar, digite", "message.confirm.upgrade.router.newer.template": "Por favor confirme que voc\u00ea deseja atualizar o roteador para usar o template mais recente.", "message.cpu.usage.info": "A porcentagem de uso da CPU pode exceder 100% se a VM tiver mais de 1 vCPU ou quando o CPU Cap n\u00E3o estiver habilitado. Este comportamento acontece de acordo com o hypervisor que est\u00E1 sendo utilizado (ex: no KVM), devido \u00E0 forma como contabilizam as estat\u00EDsticas", +"message.create.backup.failed": "Falha ao criar backup.", +"message.create.bucket.failed": "Falha ao criar bucket.", +"message.create.bucket.processing": "Cria\u00e7\u00e3o de bucket em andamento", "message.create.compute.offering": "Oferta de computa\u00e7\u00e3o criada", "message.create.internallb": "Criando LB interno", "message.create.internallb.failed": "Falha ao criar LB interno", "message.create.internallb.processing": "Cria\u00e7\u00e3o do LB interno em progresso", "message.create.service.offering": "Oferta de servi\u00e7o criada", +"message.create.sharedfs.failed": "Falha ao criar Sistema de Arquivos Compartilhado.", +"message.create.sharedfs.processing": "Cria\u00e7\u00e3o de Sistema de Arquivos Compartilhado em andamento.", "message.create.snapshot.from.vmsnapshot.failed": "Falha ao criar snapshot a partir de uma snapshot de VM", "message.create.snapshot.from.vmsnapshot.progress": "Cria\u00e7\u00e3o de snapshot em progresso", +"message.create.template.failed": "Falha ao criar template.", +"message.create.template.processing": "Cria\u00e7\u00e3o de template em andamento", +"message.create.tungsten.public.network": "Criar Rede p\u00fablica Tungsten-Fabric", "message.create.volume.failed": "Falha ao criar volume", "message.create.volume.processing": "Cria\u00e7\u00e3o de volume em progresso", "message.create.vpc.offering": "Oferta VPC criada", "message.create.vpn.customer.gateway.failed": "A cria\u00e7\u00e3o do gateway da VPN do cliente falhou", +"message.creating.autoscale.scaledown.conditions": "Criando condi\u00e7\u00f5es de ScaleDown", +"message.creating.autoscale.scaledown.policy": "Criando pol\u00edtica de ScaleDown", +"message.creating.autoscale.scaleup.conditions": "Criando condi\u00e7\u00f5es de ScaleUp", +"message.creating.autoscale.scaleup.policy": "Criando pol\u00edtica de ScaleUp", +"message.creating.autoscale.vmgroup": "Criando Grupo de AutoScale", +"message.creating.autoscale.vmprofile": "Criando perfil de Inst\u00e2ncia de AutoScale", "message.creating.cluster": "Criando cluster", "message.creating.guest.network": "Criando rede guest", "message.creating.physical.networks": "Criando redes fisicas", @@ -2058,9 +2984,13 @@ "message.delete.acl.rule": "Remover regra ACL", "message.delete.acl.rule.failed": "Falha ao remover regra ACL", "message.delete.affinity.group": "Por favor, confirme que voc\u00ea deseja remover este grupo de afinidade", +"message.delete.asn.range": "ASN Range exclu\u00eddo com sucesso", "message.delete.backup": "Voc\u00ea tem certeza de que quer apagar o backup?", "message.delete.failed": "Falha ao remover", "message.delete.gateway": "Favor confirmar que voc\u00ea deseja deleta o gateway", +"message.delete.ip.v6.prefix.processing": "Excluindo prefixo IPv6...", +"message.delete.keypair": "Favor confirmar que voc\u00ea deseja deletar a chave de API.", +"message.delete.keypair.failed": "Falha ao deletar par de chave de API", "message.delete.port.forward.processing": "Removendo regra de encaminhamento de porta...", "message.delete.project": "Voc\u00ea tem certeza que deseja remover este projeto?", "message.delete.rule.processing": "Removendo regra...", @@ -2069,44 +2999,70 @@ "message.delete.tag.failed": "Falha ao remover etiqueta", "message.delete.tag.for.networkacl": "Remover etiqueta para rede ACL", "message.delete.tag.processing": "Removendo etiqueta...", +"message.delete.traffic.type.processing": "Excluindo tipo de tr\u00e1fego...", +"message.delete.tungsten.policy.rule": "Por favor, confirme que voc\u00ea deseja excluir a Regra de Pol\u00edtica?", +"message.delete.tungsten.tag": "Tem certeza de que deseja remover esta Tag desta Pol\u00edtica?", "message.delete.user": "Por favor confirme que voc\u00ea deseja deletar este usu\u00e1rio.", "message.delete.vpn.connection": "Favor confirmar que voc\u00ea deseja deletar esta conex\u00e3o VPN", "message.delete.vpn.customer.gateway": "Favor confirmar que voc\u00ea deseja deletar este gateway de VPN de usu\u00e1rio", "message.delete.vpn.gateway": "Favor confirmar que voc\u00ea deseja deletar este gateway de VPN", +"message.delete.webhook": "Por favor, confirme que voc\u00ea deseja excluir este Webhook.", +"message.delete.webhook.delivery": "Por favor, confirme que voc\u00ea deseja excluir esta entrega de Webhook.", +"message.deleting.firewall.policy": "Excluindo Pol\u00edtica de Firewall", "message.deleting.node": "Removendo nodo", "message.deleting.vm": "Removendo VM", +"message.denied": "Negado", "message.deployasis": "O modelo selecionado \u00e9 Deploy As-Is, ou seja, a VM \u00e9 implantada atrav\u00e9s da importa\u00e7\u00e3o de um OVA com vApps diretamente no vCenter. O redimensionamento do(s) disco(s) raiz(s) \u00e9 permitido somente em VMs paradas para tais modelos.", "message.desc.advanced.zone": "Para topologias de rede mais sofisticadas. este modelo fornece maior flexibilidade na defini\u00e7\u00e3o de redes de clientes e fornece ofertas de rede personalizadas, tais como firewall, VPN ou de balanceamento de carga.", "message.desc.basic.zone": "Fornece uma \u00fanica rede onde em cada inst\u00e2ncia de VM \u00e9 atribu\u00eddo um IP diretamente na rede. O isolamento guest podem ser fornecidos atrav\u00e9s de camada-3 da rede com grupos de seguran\u00e7a (filtragem da fonte de endere\u00e7os IP).", +"message.desc.core.zone": "Zonas Core destinam-se a implanta\u00e7\u00f5es baseadas em Datacenter e permitem toda a gama de funcionalidades de Rede e outras no Apache CloudStack. Zonas Core t\u00eam uma s\u00e9rie de pr\u00e9-requisitos e dependem da presen\u00e7a de armazenamento compartilhado e inst\u00e2ncias auxiliares.", "message.desc.cluster": "Cada pod deve conter um ou mais clusters, e iremos adicionar o primeiro cluster agora. Um cluster fornece uma maneira de agrupamento de hosts. Os hosts de um cluster t\u00eam hardware id\u00eantico, executam o mesmo virtualizador, est\u00e3o na mesma sub-rede e acessam o mesmo armazenamento compartilhado. Cada cluster \u00e9 constitu\u00eddo por um ou mais hosts e um ou mais servidores de armazenamento prim\u00e1rio.", "message.desc.create.ssh.key.pair": "Por favor, preencha os seguintes dados para criar ou registar um par de chaves SSH.

(1) Se a chave p\u00fablica est\u00e1 definida, CloudStack ir\u00e1 registrar a chave p\u00fablica. Voc\u00ea pode us\u00e1-la atrav\u00e9s de sua chave privada.

(2) Se a chave p\u00fablica n\u00e3o est\u00e1 definida, CloudStack ir\u00e1 criar um novo par de chaves SSH. Neste caso, copie e salve a chave privada. CloudStack n\u00e3o ir\u00e1 mant\u00ea-la.
", "message.desc.created.ssh.key.pair": "Par de chaves SSH criado", -"message.desc.host": "Cada cluster deve conter pelo menos um host (computador) para as VMs guest serem executadas e iremos adicionar o primeira host agora. Para um host funcionar no CloudStack, voc\u00ea deve instalar um virtualizador no host, atribuir um endere\u00e7o IP e garantir que o host est\u00e1 conectado ao servidor de gerenciamento do CloudStack.

Forne\u00e7a o hostname ou o endere\u00e7o IP do host, o nome de usu\u00e1rio (geralmente root) e a senha e qualquer label que voc\u00ea utiliza para categorizar os hosts.", +"message.desc.edge.zone": "Zonas Edge s\u00e3o zonas leves, projetadas para implanta\u00e7\u00e3o em cen\u00e1rios de computa\u00e7\u00e3o de borda. Elas s\u00e3o limitadas em funcionalidade, mas t\u00eam muito menos pr\u00e9-requisitos do que zonas core.

Por favor, consulte a documenta\u00e7\u00e3o do Apache CloudStack para mais informa\u00e7\u00f5es sobre Tipos de Zona
http://docs.cloudstack.apache.org/en/latest/installguide/configuration.html#adding-a-zone", +"message.desc.host": "Cada cluster deve conter pelo menos um host (computador) para as VMs guest serem executadas e iremos adicionar o primeira host agora. Para um host funcionar no CloudStack, voc\u00ea deve instalar um virtualizador no host, atribuir um endere\u00e7o IP e garantir que o host est\u00e1 conectado ao Management Server do CloudStack.

Forne\u00e7a o hostname ou o endere\u00e7o IP do host, o nome de usu\u00e1rio (geralmente root) e a senha e qualquer label que voc\u00ea utiliza para categorizar os hosts.", +"message.desc.import.ext.kvm.wizard": "Importar dom\u00ednio libvirt de Host KVM Externo n\u00e3o gerenciado pelo CloudStack", +"message.desc.import.local.kvm.wizard": "Importar imagem QCOW2 do Armazenamento Local do Host KVM selecionado", +"message.desc.import.shared.kvm.wizard": "Importar imagem QCOW2 do Pool de Armazenamento Prim\u00e1rio selecionado", +"message.desc.import.unmanage.volume": "Por favor, escolha um pool de armazenamento do qual voc\u00ea deseja importar ou parar de gerenciar volumes. O pool de armazenamento deve estar no status Up.
Este recurso suporta apenas KVM.", "message.desc.importexportinstancewizard": "Testa caracter\u00edstica s\u00f3 se aplica aos clusters Cloudstack VMware. Ao optar por gerenciar uma inst\u00e2ncia, o CloudStack assume a orquestra\u00e7\u00e3o dessa inst\u00e2ncia. A inst\u00e2ncia \u00e9 deixada em funcionamento e n\u00e3o movida fisicamente. Desagrupar inst\u00e2ncias, remove a capacidade do CloudStack de gerenci\u00e1-las (mas elas s\u00e3o deixadas em funcionamento e n\u00e3o s\u00e3o destru\u00eddas).", +"message.desc.importingestinstancewizard": "Este recurso aplica-se apenas a inst\u00e2ncias KVM baseadas em libvirt. Apenas inst\u00e2ncias Paradas podem ser ingeridas", +"message.desc.importmigratefromvmwarewizard": "Ao selecionar um Datacenter VMware existente ou externo e uma inst\u00e2ncia para importar, o CloudStack migra a inst\u00e2ncia selecionada do VMware para KVM em um host de convers\u00e3o usando virt-v2v e a importa para um cluster KVM", "message.desc.primary.storage": "Cada cluster deve conter um ou mais servidores de armazenamento prim\u00e1rio e iremos adicionar o primeiro agora. Um armazenamento prim\u00e1rio, cont\u00e9m os volumes de disco para todas as VMs em execu\u00e7\u00e3o nos hosts do cluster. utiliza qualquer protocolo compat\u00edvel com os padr\u00f5es que \u00e9 suportado pelo virtualizador utilizado.", +"message.desc.register.user.data": "Por favor, preencha os seguintes dados para registrar um dado de usu\u00e1rio.", +"message.desc.registered.user.data": "Dados do Usu\u00e1rio Registrados.", "message.desc.reset.ssh.key.pair": "Por favor, especifique um par de chaves SSH que voc\u00ea deseja adicionar a esta VM.", "message.desc.secondary.storage": "Cada zona deve ter pelo menos um NFS ou servidor de armazenamento secund\u00e1rio e iremos adicionar o primeiro agora. Um armazenamento secund\u00e1rios armazena templates de VM, imagens ISO e snapshots do volume de disco da VM. Esse servidor deve estar dispon\u00edvel para todos os hosts na zona.

Fornecer o endere\u00e7o IP e o caminho exportados.", +"message.desc.zone.edge": "Uma zona \u00e9 a maior unidade organizacional no CloudStack e, normalmente, corresponde a um \u00fanico datacenter. Zonas fornecem isolamento f\u00edsico e redund\u00e2ncia. Uma zona de borda consiste em um ou mais hosts (cada um dos quais fornece armazenamento local como servidores de armazenamento prim\u00e1rio). Apenas redes compartilhadas e L2 podem ser implantadas nessas zonas e funcionalidades que requerem armazenamentos secund\u00e1rios n\u00e3o s\u00e3o suportadas.", "message.desc.zone": "Uma zona \u00e9 a maior unidade organizacional no CloudStack e normalmente corresponde \u00e0 um \u00fanico datacenter. As zonas disponibilizam isolamento f\u00edsico e redund\u00e2ncia. Uma zona \u00e9 composta por um ou mais pods (cada um dos quais cont\u00e9m os hosts e servidores de armazenamento prim\u00e1rio) e um servidor de armazenamento secund\u00e1rio que \u00e9 compartilhado por todos os pods na zona.", "message.detach.disk": "Voc\u00ea tem certeza que deseja desconectar este disco?", "message.detach.iso.confirm": "Confirme se voc\u00ea deseja desconectar o ISO da inst\u00e2ncia virtual.", "message.disable.account": "Por favor confirme que voc\u00ea deseja desabilitar esta conta. Ap\u00f3s desabilitar uma conta, todos os usu\u00e1rios desta conta n\u00e3o ir\u00e3o possuir mais acesso aos seus recursos da cloud. Todas as m\u00e1quinas virtuais ser\u00e3o automaticamente desligadas.", +"message.disable.role": "Por favor, confirme que voc\u00ea gostaria de desativar esta Fun\u00e7\u00e3o", "message.disable.user": "Por favor confirme que voc\u00ea deseja desabilitar este usu\u00e1rio.", "message.disable.vpn": "Voc\u00ea tem certeza que deseja desabilitar a VPN?", "message.disable.vpn.failed": "Falha ao desabilitar VPN", "message.disable.vpn.processing": "Desabilitando VPN...", +"message.disable.webhook.ssl.verification": "Desativar a verifica\u00e7\u00e3o SSL n\u00e3o \u00e9 recomendado", "message.discovering.feature": "Descobrindo funcionalidades, por favor aguarde...", "message.disk.offering.created": "Oferta de disco criada:", "message.disk.usage.info.data.points": "Cada ponto no gr\u00e1fico representa a diferen\u00e7a de dados lidos/escritos desde a \u00faltima coleta de estat\u00edstica realizada (o ponto anterior)", "message.disk.usage.info.sum.of.disks": "O uso de disco apresentado \u00e9 composto pela soma de dados lidos/escritos por todos os discos da VM", "message.download.volume": "Clique 00000 para baixar o disco", "message.download.volume.confirm": "Por favor confirme que voc\u00ea quer baixar este volume", +"message.drs.plan.description": "O n\u00famero m\u00e1ximo de migra\u00e7\u00f5es ao vivo permitidas para DRS. Configure DRS na aba de configura\u00e7\u00f5es antes de gerar um plano ou para ativar o DRS autom\u00e1tico para o cluster.", +"message.drs.plan.executed": "Plano DRS executado com sucesso.", "message.edit.acl.failed": "Falha ao editar regra ACL", "message.edit.acl.processing": "Editando regra ACL...", "message.edit.rule.failed": "Falha ao editar regra", "message.edit.rule.processing": "Atualizando regra...", "message.edit.traffic.type": "Favor especificar a etiqueta de tr\u00e1fego que voc\u00ea deseja associar com este tipo de tr\u00e1fego.", +"message.egress.rules.allow": "Permitir (o tr\u00e1fego correspondente \u00e0s regras de sa\u00edda adicionadas ser\u00e1 negado)", +"message.egress.rules.deny": "Negar (o tr\u00e1fego correspondente \u00e0s regras de sa\u00edda adicionadas ser\u00e1 permitido)", +"message.egress.rules.info.for.network": "A pol\u00edtica de sa\u00edda padr\u00e3o desta Rede \u00e9 %x.
O tr\u00e1fego de sa\u00edda correspondente \u00e0s seguintes regras de sa\u00edda ser\u00e1 %y", "message.enable.account": "Confirme se voc\u00ea deseja ativar a conta.", "message.enable.netsacler.provider.failed": "Falha ao habilitar provedor Netscaler", +"message.enable.role": "Por favor, confirme que voc\u00ea deseja ativar esta Fun\u00e7\u00e3o", "message.enable.securitygroup.provider.failed": "Falha ao habilitar provedor do grupo de seguran\u00e7a", "message.enable.user": "Por favor confirme que voc\u00ea deseja habilitar este usu\u00e1rio.", "message.enable.vpn": "Por favor confirme que voc\u00ea deseja acesso VPN habilitado para este endere\u00e7o IP.", @@ -2118,23 +3074,46 @@ "message.enter.valid.nic.ip": "Por favor insira um endere\u00e7o IP valido para o NIC", "message.error.access.key": "Por favor insira a chave de acesso", "message.error.add.guest.network": "Os campos IPv4 ou IPv6 precisam ser preenchidos ao adicionar uma rede guest", +"message.error.delete.asnrange": "Excluindo Faixa AS", +"message.error.add.interface.static.route": "Adicionar Rota Est\u00e1tica de interface falhou", +"message.error.add.logical.router": "Adicionar Roteador L\u00f3gico falhou", +"message.error.add.network.static.route": "Adicionar Rota Est\u00e1tica de Rede falhou", +"message.error.add.policy.rule": "Adicionar regra de Pol\u00edtica falhou", "message.error.add.secondary.ipaddress": "Houve um erro ao adicionar o endere\u00e7o IP secund\u00e1rio", +"message.error.add.tungsten.router.table": "Adicionar Tabela de Roteador falhou", +"message.error.add.tungsten.routing.policy": "Adicionar Pol\u00edtica de Roteamento Tungsten-Fabric falhou", "message.error.agent.password": "Por favor, insira a senha do agente", -"message.error.agent.username": "Por favor, insira o usu\u00e1rio do agent", +"message.error.agent.username": "Por favor, insira o usu\u00e1rio do agente", +"message.error.apply.network.policy": "Aplica\u00e7\u00e3o da Pol\u00edtica de Rede falhou", +"message.error.apply.tungsten.tag": "Aplica\u00e7\u00e3o de Tag falhou", +"message.error.authentication.code": "Por favor, insira o c\u00f3digo de autentica\u00e7\u00e3o.", "message.error.binaries.iso.url": "Por favor, digite a URL ISO dos bin\u00e1rios", "message.error.bucket": "Por favor, insira o bucket", +"message.error.cidr": "CIDR \u00e9 obrigat\u00f3rio", +"message.error.cidr.or.cidrsize": "CIDR ou tamanho do cidr \u00e9 obrigat\u00f3rio", "message.error.cloudian.console": "Single-Sign-On falhou para o Cloudian management console. Solicite a seu administrador que conserte problemas de integra\u00e7\u00e3o.", "message.error.cluster.description": "Por favor, digite a descri\u00e7\u00e3o do Kubernetes cluster", "message.error.cluster.name": "Por favor, insira o nome do cluster", "message.error.confirm.password": "Por favor, confirme a nova senha", +"message.error.confirm.text": "Por favor, insira o texto de confirma\u00e7\u00e3o", +"message.error.create.webhook.local.account": "Conta deve ser fornecida para criar um Webhook com escopo Local.", +"message.error.create.webhook.name": "Nome deve ser fornecido para criar um Webhook.", +"message.error.create.webhook.payloadurl": "URL de Payload deve ser fornecida para criar um Webhook.", "message.error.current.password": "Por favor, insira a senha atual", "message.error.custom.disk.size": "Por favor, insira o tamanho do disco personalizado", "message.error.date": "Por favor, insira uma data", +"message.error.delete.interface.static.route": "Remo\u00e7\u00e3o de Rota Est\u00e1tica de interface falhou", +"message.error.delete.network.static.route": "Remo\u00e7\u00e3o de Rota Est\u00e1tica de Rede falhou", +"message.error.delete.tungsten.policy.rule": "Exclus\u00e3o da regra de Pol\u00edtica falhou", +"message.error.delete.tungsten.router.table": "Remo\u00e7\u00e3o da Tabela de Roteador falhou", +"message.error.delete.tungsten.tag": "Remo\u00e7\u00e3o da Tag falhou", "message.error.description": "Por favor, insira uma descri\u00e7\u00e3o", "message.error.discovering.feature": "Exce\u00e7\u00e3o lan\u00e7ada durante a descoberta de funcionalidades", "message.error.display.text": "Por favor, insira o texto de exibi\u00e7\u00e3o", +"message.error.duration.less.than.interval": "A dura\u00e7\u00e3o na pol\u00edtica de AutoScale n\u00e3o pode ser menor que o intervalo", "message.error.enable.saml": "Incapaz de encontrar IDs de usu\u00e1rios para ativar o SAML Single Sign On, por favor, ative-o manualmente.", "message.error.end.date.and.time": "Por favor, selecione a data e hor\u00e1rio final.", +"message.error.endasn": "Por favor, insira a Faixa AS final", "message.error.endip": "Por favor, insira o IP final", "message.error.gateway": "Por favor, insira o gateway", "message.error.host.name": "Por favor, insira o nome do host", @@ -2147,9 +3126,11 @@ "message.error.internallb.instance.port": "Por favor, especifique a porta da inst\u00e2ncia", "message.error.internallb.name": "Por favor, especifique um nome para o LB interno", "message.error.internallb.source.port": "Por favor, insira uma porta de origem", +"message.error.invalid.autoscale.vmgroup.name": "Nome do Grupo de AutoScale inv\u00e1lido. Ele pode conter as letras ASCII 'a' a 'z', 'A' a 'Z', os d\u00edgitos '0' a '9' e o h\u00edfen ('-'), deve ter entre 1 e 255 caracteres.", "message.error.ip.range": "Por favor, insira um intervalo v\u00e1lido", "message.error.ipv4.address": "Por favor, insira um endere\u00e7o IPv4 v\u00e1lido", "message.error.ipv4.dns1": "Por favor, insira o DNS 1 IPv4", +"message.error.ipv4.dns2": "Por favor, insira o DNS 2 IPv4", "message.error.ipv6.address": "Por favor, insira um endere\u00e7o IPv6 v\u00e1lido.", "message.error.ipv6.gateway": "Por favor, insira o gateway IpV6", "message.error.ipv6.gateway.format": "Por favor, insira um gateway IpV6 v\u00e1lido.", @@ -2159,6 +3140,10 @@ "message.error.loading.setting": "Houve um erro ao carregar estas configura\u00e7\u00f5es.", "message.error.lun": "Por favor, insira o # LUN", "message.error.macaddress": "Por favor, digite um endere\u00e7o MAC v\u00e1lido.", +"message.error.max.members.less.than.min.members": "O valor de M\u00e1x membros deve ser maior que o valor de M\u00edn membros", +"message.error.mtu.below.min": "MTU est\u00e1 abaixo do valor m\u00ednimo suportado de %x", +"message.error.mtu.private.max.exceed": "O valor inserido excede o MTU privado m\u00e1ximo permitido para esta Zona, seu valor ser\u00e1 automaticamente reduzido para corresponder a ele.", +"message.error.mtu.public.max.exceed": "O valor inserido excede o MTU p\u00fablico m\u00e1ximo permitido para esta Zona, seu valor ser\u00e1 automaticamente reduzido para corresponder a ele.", "message.error.name": "Por favor, insira um nome", "message.error.netmask": "Por favor, insira am\u00e1scara de rede", "message.error.network.offering": "Por favor, selecione a oferta de rede", @@ -2167,6 +3152,7 @@ "message.error.nexus1000v.password": "Por favor, insira a senha do Nexus 1000v", "message.error.nexus1000v.username": "Por favor, insira o usu\u00e1rio do Nexus 1000v", "message.error.number": "Por favor, insira um n\u00famero v\u00e1lido", +"message.error.parent.subnet": "Por favor, escolha uma sub-rede pai", "message.error.password": "Insira a sua senha", "message.error.path": "Por favor, insira o path", "message.error.provide.setting": "Deve fornecer uma chave v\u00e1lida e um valor para a configura\u00e7\u00e3o", @@ -2174,10 +3160,16 @@ "message.error.rados.pool": "Por favor, digite o RADOS pool", "message.error.rados.secret": "Por favor, digite o RADOS secret", "message.error.rados.user": "Por favor, digite o usu\u00e1rio RADOS", +"message.error.remove.logical.router": "Remo\u00e7\u00e3o do Roteador L\u00f3gico falhou", +"message.error.remove.network.policy": "Remo\u00e7\u00e3o da Pol\u00edtica de Rede falhou", "message.error.remove.nic": "Houve um erro", "message.error.remove.secondary.ipaddress": "Houve um erro ao remover o endere\u00e7o IP secund\u00e1rio", +"message.error.remove.tungsten.routing.policy": "Remo\u00e7\u00e3o da Pol\u00edtica de Roteamento Tungsten-Fabric da Rede falhou", +"message.error.remove.vm.schedule": "Remo\u00e7\u00e3o do Agendamento de Inst\u00e2ncia falhou", "message.error.required.input": "Por favor, digite os dados", +"message.error.reset.config": "Incapaz de redefinir configura\u00e7\u00e3o para o valor padr\u00e3o", "message.error.retrieve.kubeconfig": "N\u00e3o foi poss\u00edvel recuperar a configura\u00e7\u00e3o do cluster Kubernetes", +"message.error.routing.policy.term": "Comunidade precisa ter o seguinte formato n\u00famero:n\u00famero", "message.error.s3nfs.path": "Por favor, insira o S3 NFS path", "message.error.s3nfs.server": "Por favor, insira o servidor S3 NFS", "message.error.save.setting": "Houve um erro ao salvar esta configura\u00e7\u00e3o.", @@ -2187,18 +3179,28 @@ "message.error.secret.key": "Por favor, insira a chave secreta", "message.error.select": "Por favor, escolha uma op\u00e7\u00e3o", "message.error.select.domain.to.dedicate": "Por favor, escolha o dom\u00ednio a ser dedicado", +"message.error.select.load.balancer": "Por favor, selecione um balanceador de carga", +"message.error.select.network": "Por favor, selecione uma Rede", +"message.error.select.network.supports.vm.autoscaling": "A Rede padr\u00e3o que voc\u00ea selecionou n\u00e3o suporta AutoScale de Inst\u00e2ncia, por favor selecione uma Rede padr\u00e3o que suporte AutoScale de Inst\u00e2ncia.", +"message.error.select.user": "Por favor, selecione um Usu\u00e1rio", "message.error.select.zone.type": "Por favor, escolha o tipo de zona abaixo.", "message.error.server": "Por favor, insira o servidor", "message.error.serviceoffering.for.cluster": "Por favor, escolha a oferta de servi\u00e7o para o cluster Kubernetes", +"message.error.setup.2fa": "Falha na configura\u00e7\u00e3o 2FA ao verificar o c\u00f3digo, por favor tente novamente.", "message.error.size": "Por favor, insira o tamanho em GB", "message.error.size.for.cluster": "Por favor, insira o tamanho do cluster Kubernetes", "message.error.smb.password": "Por favor, insira a senha do SMB ", "message.error.smb.username": "Por favor, insira o usu\u00e1rio do SMB", +"message.error.specify.stickiness.method": "Por favor, especifique um m\u00e9todo de stickiness", "message.error.specify.sticky.name": "Por favor, especifique o nome sticky", "message.error.sr.namelabel": "Por favor, insira o nome-R\u00f3tulo SR", "message.error.start.date.and.time": "Por favor, selecione a data e hor\u00e1rio inicial.", +"message.error.startasn": "Por favor, insira a Faixa AS inicial", "message.error.startip": "Por favor, insira o IP de \u00cdnicio", "message.error.storage.tags": "Por favor, insira as tags de armazenamento", +"message.error.swift.account": "Por favor, insira a Conta", +"message.error.swift.key": "Por favor, insira a chave", +"message.error.swift.username": "Por favor, insira o nome de usu\u00e1rio", "message.error.target.iqn": "Por favor, insira o Target IQN", "message.error.time": "Por favor, selecione a hora", "message.error.traffic.label": "Por favor, insira o r\u00f3tulo de tr\u00e1fego", @@ -2207,6 +3209,7 @@ "message.error.upload.template": "Falha no carregamento de template", "message.error.upload.template.description": "Apenas um template pode ser carregado de cada vez", "message.error.url": "Por favor, insira a URL", +"message.error.userdata": "Por favor, insira o Userdata", "message.error.username": "Insira o seu usu\u00e1rio", "message.error.valid.iops.range": "Por favor, insira uma faixa IOPS v\u00e1lida", "message.error.vcenter.datacenter": "Por favor, insira o vCenter datacenter", @@ -2214,6 +3217,7 @@ "message.error.vcenter.host": "Por favor, insira o vCenter host", "message.error.vcenter.password": "Por favor, insira a senha vCenter", "message.error.vcenter.username": "Por favor, insira o usu\u00e1rio vCenter", +"message.error.verifying.2fa": "Incapaz de verificar 2FA, por favor tente novamente.", "message.error.version.for.cluster": "Por favor, selecionar a vers\u00e3o Kubernetes para o cluster Kubernetes", "message.error.vlan.range": "Por favor, insira um intervalo VLAN/VNI v\u00e1lido", "message.error.volume.name": "Por favor, insira o nome do volume", @@ -2225,19 +3229,45 @@ "message.error.zone.name": "Por favor, insira o nome da zona", "message.error.zone.type": "Por favor, selecione o tipo de zona", "message.error.linstor.resourcegroup": "Por favor, insira o Linstor Resource-Group", -"message.error.fixed.offering.kvm": "N\u00e3o \u00e9 poss\u00edvel escalar VMs que utilizam o virtualizador KVM com uma oferta de computa\u00e7\u00e3o fixa.", "message.fail.to.delete": "Falha ao deletar.", "message.failed.to.add": "Falha ao adicionar", "message.failed.to.assign.vms": "Falha ao atribuir VMs", "message.failed.to.remove": "Falha ao remover", +"message.forgot.password.success": "Um e-mail foi enviado para o seu endere\u00e7o de e-mail com instru\u00e7\u00f5es sobre como redefinir sua senha.", "message.generate.keys": "Por favor confirme que voc\u00ea deseja gerar novas chaves para este usu\u00e1rio.", "message.chart.statistic.info": "Os gr\u00E1ficos mostrados s\u00E3o autoajust\u00E1veis, ou seja, se o valor se aproximar ou passar do limite, ele crescer\u00E1 para ajustar o valor mostrado", +"message.chart.statistic.info.hypervisor.additionals": "Os dados de m\u00e9tricas dependem do plugin de hypervisor usado para cada hypervisor. O comportamento pode variar entre diferentes hypervisors. Por exemplo, com KVM, as m\u00e9tricas s\u00e3o estat\u00edsticas em tempo real fornecidas pelo libvirt. Em contraste, com VMware, as m\u00e9tricas s\u00e3o dados m\u00e9dios para um determinado intervalo de tempo controlado pela configura\u00e7\u00e3o.", "message.guest.traffic.in.advanced.zone": "O tr\u00e1fego de rede guest \u00e9 para comunica\u00e7\u00e3o entre m\u00e1quinas virtuais do usu\u00e1rio final. Especifique um intervalo de IDs de VLAN para transportar o tr\u00e1fego do guest para cada rede f\u00edsica.", "message.guest.traffic.in.basic.zone": "O tr\u00e1fego de rede guest \u00e9 para comunica\u00e7\u00e3o entre m\u00e1quinas virtuais do usu\u00e1rio final. Especifique um intervalo de endere\u00e7os IP para que CloudStack possa atribuir \u00e0s VMs. Certifique-se que este intervalo n\u00e3o se sobreponha o range de IPs reservados do sistema.", +"message.host.controlstate": "O Status do Recurso de Computa\u00e7\u00e3o para esta Inst\u00e2ncia \u00e9 ", +"message.host.controlstate.retry": "Algumas a\u00e7\u00f5es nesta Inst\u00e2ncia falhar\u00e3o, se for o caso, por favor aguarde um pouco e tente novamente.", "message.host.dedicated": "Host dedicado", "message.host.dedication.released": "Host dedicado liberado", "message.info.cloudian.console": "O Cloudian management console deve abrir em outra janela", -"message.installwizard.copy.whatiscloudstack": "O CloudStack™ \u00e9 uma plataforma de software que agrega recursos computacionais para construir uma Cloud de infraestrutura como servi\u00e7o (IaaS) p\u00fablica, privada ou h\u00edbrida. O CloudStack™ ger\u00eancia a rede, o armazenamento e os recursos computacionais que comp\u00f5em a infraestrutura de cloud. utilize o CloudStack™ para instalar, gerenciar e configurar os ambientes de cloud computing.

Indo al\u00e9m de imagens de m\u00e1quinas virtuais individuais rodando em hardware commodity, CloudStack™ prov\u00ea uma solu\u00e7\u00e3o completa de software de infraestrutura de cloud para entregar datacenters virtuais como um servi\u00e7o - possuindo todos os componentes essenciais para contruir, instalar e gerenciar aplica\u00e7\u00f5es na cloud multi-camadas e multi-tenant.", +"message.infra.setup.nsx.description": "Esta zona deve conter um provedor NSX porque o m\u00e9todo de isolamento \u00e9 NSX", +"message.infra.setup.tungsten.description": "Esta zona deve conter um provedor Tungsten-Fabric porque o m\u00e9todo de isolamento \u00e9 TF", +"message.import.running.instance.warning": "A VM selecionada est\u00e1 ligada no Datacenter VMware. O estado recomendado para converter uma VM VMware em KVM \u00e9 desligada ap\u00f3s um desligamento gracioso do SO convidado.", +"message.import.volume": "Por favor, especifique o dom\u00ednio, conta ou nome do projeto.
Se n\u00e3o definido, o volume ser\u00e1 importado para o solicitante.", +"message.installwizard.cloudstack.helptext.document": " * Documenta\u00e7\u00e3o:\t ", +"message.installwizard.cloudstack.helptext.header": "\nVoc\u00ea pode encontrar mais informa\u00e7\u00f5es sobre o Apache CloudStack™ nas p\u00e1ginas listadas abaixo.\n", +"message.installwizard.cloudstack.helptext.issues": " * Relatar problemas:\t ", +"message.installwizard.cloudstack.helptext.mailinglists": " * Juntar-se a listas de discuss\u00e3o:\t ", +"message.installwizard.cloudstack.helptext.releasenotes": " * Notas de lan\u00e7amento:\t ", +"message.installwizard.cloudstack.helptext.survey": " * Participe da pesquisa:\t ", +"message.installwizard.cloudstack.helptext.website": " * Site do projeto:\t ", +"message.installwizard.tooltip.nsx.provider.edgecluster": "Informa\u00e7\u00f5es do cluster de borda do Provedor NSX n\u00e3o fornecidas", +"message.installwizard.tooltip.nsx.provider.hostname": "Hostname / endere\u00e7o IP do Provedor NSX n\u00e3o fornecido", +"message.installwizard.tooltip.nsx.provider.password": "Senha do Provedor NSX n\u00e3o fornecida", +"message.installwizard.tooltip.nsx.provider.tier0gateway": "Informa\u00e7\u00f5es do gateway tier-0 do Provedor NSX n\u00e3o fornecidas", +"message.installwizard.tooltip.nsx.provider.transportZone": "Informa\u00e7\u00f5es da zona de transporte do Provedor NSX n\u00e3o fornecidas", +"message.installwizard.tooltip.nsx.provider.username": "Nome de usu\u00e1rio do Provedor NSX n\u00e3o fornecido", +"message.installwizard.tooltip.tungsten.provider.gateway": "Gateway do provedor Tungsten \u00e9 obrigat\u00f3rio", +"message.installwizard.tooltip.tungsten.provider.hostname": "Hostname do provedor Tungsten \u00e9 obrigat\u00f3rio", +"message.installwizard.tooltip.tungsten.provider.introspectport": "Porta de introspec\u00e7\u00e3o do provedor Tungsten \u00e9 obrigat\u00f3ria", +"message.installwizard.tooltip.tungsten.provider.name": "Nome do provedor Tungsten \u00e9 obrigat\u00f3rio", +"message.installwizard.tooltip.tungsten.provider.port": "Porta do provedor Tungsten \u00e9 obrigat\u00f3ria", +"message.installwizard.tooltip.tungsten.provider.vrouterport": "Porta do vrouter do provedor Tungsten \u00e9 obrigat\u00f3ria", +"message.installwizard.copy.whatiscloudstack": "O CloudStack™ \u00e9 uma plataforma de software que agrega recursos computacionais para construir uma Cloud de infraestrutura como servi\u00e7o (IaaS) p\u00fablica, privada ou h\u00edbrida. O CloudStack™ ger\u00eancia a rede, o armazenamento e os recursos computacionais que comp\u00f5em a infraestrutura de cloud. utilize o CloudStack™ para instalar, gerenciar e configurar os ambientes de cloud computing.

Indo al\u00e9m de imagens de m\u00e1quinas virtuais individuais rodando em hardware commodity, CloudStack™ prov\u00ea uma solu\u00e7\u00e3o completa de software de infraestrutura de cloud para entregar datacenters virtuais como um servi\u00e7o - possuindo todos os componentes essenciais para contruir, instalar e gerenciar aplica\u00e7\u00f5es na cloud multi-camadas e multi-tenant. Ambas as vers\u00f5es open-source e premium est\u00e3o dispon\u00edveis, com a vers\u00e3o opensource oferecendo praticamente os mesmos recursos.", "message.installwizard.tooltip.addpod.name": "O nome para o pod", "message.installwizard.tooltip.addpod.reservedsystemendip": "Este \u00e9 o range de IP na rede privada que o CloudStack utiliza para gerenciar o armazenamento secund\u00e1rio das VMs e proxy console das VMs. estes endere\u00e7os IP s\u00e3o obtidos da mesma subrede dos servidores hosts.", "message.installwizard.tooltip.addpod.reservedsystemgateway": "O gateway para os hosts neste pod.", @@ -2248,8 +3278,12 @@ "message.installwizard.tooltip.configureguesttraffic.gueststartip": "O range de endere\u00e7os IP que estar\u00e1 dispon\u00edvel para aloca\u00e7\u00e3o para os guests nesta zona. Caso uma interface de rede seja utilizada, estes IPs devem estar no mesmo CIDR que o CIDR do pod.", "message.instances.managed": "Inst\u00e2ncias ou VMs controladas pelo CloudStack", "message.instances.unmanaged": "Inst\u00e2ncias ou VMs que n\u00e3o s\u00e3o controladas pelo CloudStack", +"message.instances.migrate.vmware": "Inst\u00e2ncias que podem ser migradas do VMware.", "message.interloadbalance.not.return.elementid": "erro: A API listInternalLoadBalancerElements API n\u00e3o retorna o ID interno do elemento LB", +"message.ip.address": "Endere\u00e7o IP : ", "message.ip.address.changes.effect.after.vm.restart": "As mudan\u00e7as de endere\u00e7o IP entram em vigor somente ap\u00f3s o rein\u00edcio da VM.", +"message.ip.v6.prefix.delete": "Prefixo IPv6 exclu\u00eddo", +"message.iso.arch": "Por favor, selecione uma arquitetura ISO", "message.iso.desc": "Imagem de disco contendo dados ou m\u00eddia de sistema operacional boot\u00e1vel", "message.kubeconfig.cluster.not.available": "kubeconfig do cluster Kubernetes n\u00e3o est\u00e1 dispon\u00edvel no momento", "message.kubernetes.cluster.delete": "Por favor, confirme que voc\u00ea quer destruir o cluster", @@ -2258,25 +3292,43 @@ "message.kubernetes.cluster.stop": "Por favor, confirme que voc\u00ea deseja parar o cluster", "message.kubernetes.cluster.upgrade": "Por favor, selecione a nova vers\u00e3o do Kubernetes", "message.kubernetes.version.delete": "Por favor, confirme que voc\u00ea deseja excluir esta vers\u00e3o do Kubernetes", +"message.l2.network.unsupported.for.nsx": "Redes L2 n\u00e3o s\u00e3o suportadas para zonas habilitadas para NSX", "message.launch.zone": "A zona est\u00e1 pronta para ser executada; por favor, v\u00e1 para o pr\u00f3ximo passo.", "message.launch.zone.description": "A zona est\u00e1 pronta para iniciar; por favor, prossiga para o pr\u00f3ximo passo.", "message.launch.zone.hint": "Configurar componentes e tr\u00e1fego de rede, incluindo endere\u00e7os IP.", "message.license.agreements.not.accepted": "Contratos de licen\u00e7a n\u00e3o foram aceitos", "message.listnsp.not.return.providerid": "erro: A API listNetworkServiceProviders n\u00e3o retorna o ID do provedor virtualRouter", "message.load.host.failed": "Falha ao carregar os hosts", +"message.loadbalancer.stickypolicy.configuration": "Personalize a pol\u00edtica de stickiness do balanceador de carga:", +"message.loading.add.interface.static.route": "Adicionando Rota Est\u00e1tica de interface...", +"message.loading.add.network.static.route": "Adicionando Rota Est\u00e1tica de Rede...", +"message.loading.add.policy.rule": "Adicionando regra de Pol\u00edtica...", +"message.loading.add.tungsten.router.table": "Adicionando Tabela de Roteador...", +"message.loading.apply.tungsten.tag": "Aplicando Tag...", +"message.loading.delete.interface.static.route": "Removendo Rota Est\u00e1tica de interface...", +"message.loading.delete.network.static.route": "Removendo Rota Est\u00e1tica de Rede...", +"message.loading.delete.tungsten.policy.rule": "Excluindo regra de Pol\u00edtica...", +"message.loading.delete.tungsten.router.table": "Removendo Tabela de Roteador...", +"message.loading.delete.tungsten.tag": "Removendo Tag...", "message.lock.account": "Confirme se voc\u00ea deseja bloquear esta conta. Bloqueando a conta, todos os usu\u00e1rios desta conta n\u00e3o estar\u00e3o mais habilitados a gerenciar os recursos na nuvem. Os recursos existentes (cloud server) ainda poder\u00e3o ser acessados.", "message.lock.user": "Confirme se voc\u00ea deseja bloquear o usu\u00e1rio \"{user}\". Bloqueando este usu\u00e1rio, o mesmo n\u00e3o estar\u00e1 mais habilitado a gerenciar os recursos na nuvem. Os recursos existentes (cloud server) ainda poder\u00e3o ser acessados.", "message.lock.user.success": "Usu\u00e1rio \"{user}\" bloqueado com sucesso", "message.login.failed": "Falha no login", +"message.list.zone.vmware.datacenter.empty": "Nenhum Datacenter VMware existe na Zona selecionada", "message.memory.usage.info.hypervisor.additionals": "Os dados apresentados podem n\u00e3o refletir o real uso de mem\u00f3ria se a VM n\u00e3o possuir as ferramentas adicionais do virtualizador instaladas", "message.memory.usage.info.negative.value": "Se n\u00e3o for poss\u00edvel obter do hypervisor o uso de mem\u00f3ria da VM, ser\u00e3o desabilitadas as linhas de mem\u00f3ria livre do gr\u00e1fico de dados brutos e de uso de mem\u00f3ria no gr\u00e1fico de percentual", +"message.migrate.instance.host.auto.assign": "O Host para a Inst\u00e2ncia ser\u00e1 escolhido automaticamente com base na adequa\u00e7\u00e3o dentro do mesmo cluster", "message.migrate.instance.to.host": "Por favor confirme que voc\u00ea deseja migrar a inst\u00e2ncia para outro host.", "message.migrate.instance.to.ps": "Por favor confirme que voc\u00ea deseja migrar a inst\u00e2ncia para outro armazenamento prim\u00e1rio.", +"message.migrate.resource.to.ss": "Por favor, confirme que voc\u00ea deseja migrar este recurso para outro armazenamento secund\u00e1rio.", "message.migrate.router.confirm": "Por favor confirme o host que voc\u00ea deseja migrar o roteador para:", "message.migrate.systemvm.confirm": "Por favor confirme o host para o qual voc\u00ea deseja migrar a VM de sistema:", "message.migrate.volume": "Por favor confirme que voc\u00ea deseja migrar o volume \"{volume}\" do armazenamento prim\u00e1rio \"{storage}\" para outro.", "message.migrate.volume.failed": "Falha ao migrar volume", +"message.migrate.volume.pool.auto.assign": "O armazenamento prim\u00e1rio para o volume ser\u00e1 escolhido automaticamente com base na adequa\u00e7\u00e3o e no destino da Inst\u00e2ncia", "message.migrate.volume.processing": "Migrando volume...", +"message.migrate.volume.tooltip": "O volume pode ser migrado para qualquer pool de armazenamento adequado. O administrador deve escolher a oferta de disco apropriada para substituir, que suporte o novo pool de armazenamento", +"message.migrate.with.storage": "Especifique o pool de armazenamento para volumes da Inst\u00e2ncia.", "message.migrating.failed": "Falha na migra\u00e7\u00e3o", "message.migrating.processing": "Migra\u00e7\u00e3o em andamento para", "message.migrating.vm.to.storage.failed": "Falha na migra\u00e7\u00e3o da VM para o armazenamento", @@ -2288,7 +3340,7 @@ "message.network.addvm.desc": "Por favor especifique a rede onde voc\u00ea gostaria de adicionar esta VM. Uma nova NIC ser\u00e1 adicionada a esta rede.", "message.network.description": "Configura\u00e7\u00e3o da rede e do tr\u00e1fego", "message.network.error": "Erro de rede", -"message.network.error.description": "N\u00e3o \u00e9 poss\u00edvel acessar o servidor de gerenciamento ou uma extens\u00e3o do navegador pode estar bloqueando a solicita\u00e7\u00e3o de rede.", +"message.network.error.description": "N\u00e3o \u00e9 poss\u00edvel acessar o Management Server ou uma extens\u00e3o do navegador pode estar bloqueando a solicita\u00e7\u00e3o de rede.", "message.network.hint": "Configure componentes de rede e tr\u00e1fego p\u00fablico/guest/gerenciamento, incluindo endere\u00e7os IP.", "message.network.offering.change.warning": "AVISO: Alterar a oferta causar\u00e1 tempo de inatividade de conectividade para as VMs com NICs na rede.", "message.network.offering.forged.transmits": "O switch deixa qualquer frame de sa\u00edda de um adaptador de m\u00e1quina virtual com um endere\u00e7o MAC de origem diferente daquele do arquivo de configura\u00e7\u00e3o .vmx.\nRejeitar - O switch deixa qualquer frame de sa\u00edda de um adaptador de m\u00e1quina virtual com um endere\u00e7o MAC de origem diferente daquele do arquivo de configura\u00e7\u00e3o .vmx.\nAceitar - O switch n\u00e3o realiza filtragem e permite todos os frames de sa\u00edda.\nNenhum - padr\u00e3o de valor a partir da configura\u00e7\u00e3o global.", @@ -2297,21 +3349,35 @@ "message.network.offering.mac.learning.warning": "AVISO: para usar o MAC Learning voc\u00ea deve assegurar-se de que os virtualizadores dos hosts est\u00e3o rodando ESXi 6.7+ e a rede usa o vSwitch 6.6.0+ distribu\u00eddo.", "message.network.offering.promiscuous.mode": "Se aplica apenas para redes guest no virtualizador VMware.\nRejeitar - O switch deixa qualquer frame de sa\u00edda de um adaptador de m\u00e1quina virtual com um endere\u00e7o MAC de origem diferente daquele do arquivo de configura\u00e7\u00e3o .vmx.\nAceitar - O switch n\u00e3o realiza filtragem e permite todos os frames de sa\u00edda.\nNenhum - padr\u00e3o de valor a partir da configura\u00e7\u00e3o global.", "message.network.removenic": "Por favor, confirme que deseja remover esta interface de rede, esta a\u00e7\u00e3o tamb\u00e9m ir\u00e1 remover a rede associada \u00e0 VM.", +"message.network.restart.required": "Reinicializa\u00e7\u00e3o \u00e9 necess\u00e1ria para rede(s). Clique aqui para ver rede(s) que requerem reinicializa\u00e7\u00e3o.", +"message.network.selection": "Escolha uma ou mais Redes para anexar a Inst\u00e2ncia.", +"message.network.selection.new.network": "Uma nova Rede tamb\u00e9m pode ser criada aqui.", "message.network.secondaryip": "Por favor, confirme que voc\u00ea gostaria de adquirir um novo IP secund\u00e1rio para este NIC. \n NOTA: Voc\u00ea precisa configurar manualmente o IP secund\u00e1rio rec\u00e9m-adquirido dentro da m\u00e1quina virtual.", "message.network.updateip": "Por favor, confirme que voc\u00ea gostaria de mudar o endere\u00e7o IP da NIC em VM.", "message.network.update.nic": "Por favor, confirme que voc\u00ea gostaria de atualizar esta NIC.", "message.network.usage.info.data.points": "Cada ponto no gr\u00e1fico representa a diferen\u00e7a de dados trafegados desde a \u00faltima coleta de estat\u00edstica realizada (o ponto anterior)", "message.network.usage.info.sum.of.vnics": "O uso de rede apresentado \u00e9 composto pela soma de dados trafegados por todas as vNICs da VM", +"message.new.version.available": "Uma nova vers\u00e3o do CloudStack est\u00e1 dispon\u00edvel. Clique aqui para verificar os detalhes", +"message.nfs.mount.options.description": "Lista separada por v\u00edrgulas de op\u00e7\u00f5es de montagem NFS para hosts KVM. Op\u00e7\u00f5es suportadas : vers=[3,4.0,4.1,4.2], nconnect=[1...16]", "message.no.data.to.show.for.period": "Nenhum dado para mostrar no per\u00edodo selecionado.", "message.no.description": "Nenhuma descri\u00e7\u00e3o inserida.", +"message.offering.internet.protocol.warning": "AVISO: Redes suportadas por IPv6 usam roteamento est\u00e1tico e exigir\u00e3o que rotas upstream sejam configuradas manualmente.", +"message.offering.ipv6.warning": "Por favor, consulte a documenta\u00e7\u00e3o para criar oferta de Rede/VPC habilitada para IPv6 Suporte IPv6 no CloudStack - Redes Isoladas e Camadas de VPC", "message.ovf.configurations": "H\u00e1 propriedades OVF dispon\u00edveis para a personaliza\u00e7\u00e3o do aparelho selecionado. Por favor, edite os valores de forma apropriada.As ofertas incompat\u00edveis de computa\u00e7\u00e3o ser\u00e3o desativadas.", +"message.password.reset.failed": "Falha ao redefinir senha.", +"message.password.reset.success": "A senha foi redefinida com sucesso. Por favor, fa\u00e7a login usando suas novas credenciais.", +"message.path": "Caminho : ", "message.path.description": "NFS: caminho exportado do servidor. VMFS: /datacenter name/datastore name. SharedMountPoint: caminho onde o armazenamento prim\u00e1rio \u00e9 montado, tal como /mnt/primary", "message.please.confirm.remove.ssh.key.pair": "Por favor, confirme que voc\u00ea deseja remover este par de chaves SSH", +"message.please.confirm.remove.user.data": "Por favor, confirme que voc\u00ea deseja remover este Userdata", "message.please.enter.valid.value": "Por favor, insira um valor v\u00e1lido", "message.please.enter.value": "Por favor, insira valores", +"message.please.wait.while.autoscale.vmgroup.is.being.created": "Por favor, aguarde enquanto seu Grupo de AutoScale est\u00e1 sendo criado; isso pode levar um tempo...", "message.please.wait.while.zone.is.being.created": "Por favor, espere enquanto sua zona est\u00e1 sendo criada; isto pode demorar um pouco...", "message.pod.dedicated": "Pod dedicado", "message.pod.dedication.released": "Pod dedicado liberado", +"message.prepare.for.shutdown": "Por favor, confirme que voc\u00ea gostaria de preparar este Management Server para desligamento. Ele n\u00e3o aceitar\u00e1 nenhum novo job Ass\u00edncrono, mas N\u00c3O terminar\u00e1 ap\u00f3s n\u00e3o haver jobs pendentes.", +"message.primary.storage.invalid.state": "Armazenamento prim\u00e1rio n\u00e3o est\u00e1 no estado Up", "message.processing.complete": "Processamento conclu\u00eddo!", "message.protocol.description": "Para XenServer, escolha NFS, iSCSI, ou PreSetup. para KVM, escolha NFS, SharedMountPoint, RDB, CLVM ou Gluster. para vSphere, escolha NFS, PreSetup (VMFS, iSCSI, fiberChannel, vSAN ou vVols) ou datastoreCluster. para Hyper-V, escolha SMB/CIFS. para LXC, escolha NFS ou SharedMountPoint. para OVM, escolha NFS ou ocfs2.", "message.public.traffic.in.advanced.zone": "O tr\u00e1fego p\u00fablico \u00e9 gerado quando as VMs na nuvem acessam a internet. Os IPs acess\u00edveis ao p\u00fablico devem ser alocados para essa finalidade. Os usu\u00e1rios finais podem usar a interface do usu\u00e1rio CloudStack para adquirir esses IPs afim de implementar NAT entre a sua rede de guests e sua rede p\u00fablica.

Forne\u00e7a pelo menos um intervalo de endere\u00e7os IP para o tr\u00e1fego de internet.", @@ -2339,6 +3405,11 @@ "message.remove.ldap": "Voc\u00ea tem certeza que deseja deletar a configura\u00e7\u00e3o LDAP?", "message.remove.nic.processing": "Removendo NIC...", "message.remove.port.forward.failed": "Falha ao remover regra de encaminhamento de porta", +"message.remove.router.table.from.interface": "Por favor, confirme que voc\u00ea deseja remover a Tabela de Roteamento desta NIC", +"message.remove.router.table.from.interface.failed": "Remo\u00e7\u00e3o da Tabela de Roteamento da interface falhou", +"message.remove.routing.firewall.rule.failed": "Falha ao remover regra de firewall de Roteamento IPv4", +"message.remove.routing.firewall.rule.processing": "Removendo regra de firewall de Roteamento IPv4...", +"message.remove.routing.firewall.rule.success": "Regra de firewall de Roteamento IPv4 removida", "message.remove.rule.failed": "Falha ao remover regra", "message.remove.secondary.ipaddress.processing": "Removendo endere\u00e7o IP secund\u00e1rio...", "message.remove.securitygroup.rule.processing": "Removendo regra do grupo de seguran\u00e7a...", @@ -2347,10 +3418,12 @@ "message.remove.vpc": "Favor confirmar que voc\u00ea deseja remover a VPC", "message.request.failed": "Falha na solicita\u00e7\u00e3o", "message.required.add.least.ip": "Por favor, adicionar pelo menos UM intervalo IP", +"message.required.tagged.physical.network": "S\u00f3 pode haver uma rede f\u00edsica n\u00e3o marcada com tipo de tr\u00e1fego convidado.", "message.required.traffic.type": "Erro na configura\u00e7\u00e3o! Todos os tipos de tr\u00e1fego necess\u00e1rios devem ser adicionados e com m\u00faltiplas redes f\u00edsicas cada rede deve ter uma etiqueta.", "message.reset.vpn.connection": "Favor confirmar que voc\u00ea deseja reinicializar a conex\u00e3o VPN", "message.linstor.resourcegroup.description": "Grupo de recursos Linstor a ser usado para armazenamento prim\u00e1rio", "message.resize.volume.failed": "Falha ao redimensionar volume", +"message.resize.volume.processing": "Redimensionamento de volume est\u00e1 em andamento", "message.resource.not.found": "Recurso n\u00e3o encontrado", "message.restart.mgmt.server": "Reinicie o(s) servidor(es) de gerenciamento para que a nova configura\u00c3\u00a7\u00c3\u00a3o tenha efeito.", "message.restart.network": "Por favor confirme que voc\ufffd deseja reiniciar a rede", @@ -2358,24 +3431,47 @@ "message.restart.vpc": "Favor confirmar que voc\u00ea deseja reiniciar a VPC", "message.restart.vpc.remark": "Por favor, confirme a reinicializa\u00e7\u00e3o do VPC

Observa\u00e7\u00e3o: fazendo um VPC redundante n\u00e3o redundante ir\u00e1 for\u00e7ar uma limpeza. As redes n\u00e3o estar\u00e3o dispon\u00edveis por alguns minutos.

", "message.scale.processing": "Escalonamento em progresso", +"message.scaledown.policies": "Por favor, adicione pelo menos uma pol\u00edtica de ScaleDown. O Grupo de AutoScale ser\u00e1 reduzido quando todas as condi\u00e7\u00f5es em uma pol\u00edtica de ScaleDown forem atendidas. As pol\u00edticas de ScaleDown ser\u00e3o verificadas ap\u00f3s as pol\u00edticas de ScaleUp.", +"message.scaledown.policy.continue": "Por favor, adicione pelo menos uma condi\u00e7\u00e3o \u00e0 pol\u00edtica de ScaleDown para continuar", +"message.scaledown.policy.duration.continue": "Por favor, insira uma dura\u00e7\u00e3o v\u00e1lida para a pol\u00edtica de ScaleDown para continuar", +"message.scaledown.policy.name.continue": "Por favor, insira um nome para a pol\u00edtica de ScaleDown para continuar", +"message.scaleup.policies": "Por favor, adicione pelo menos uma pol\u00edtica de ScaleUp. O Grupo de AutoScale ser\u00e1 aumentado quando todas as condi\u00e7\u00f5es em uma pol\u00edtica de ScaleUp forem atendidas. As pol\u00edticas de ScaleUp ser\u00e3o verificadas antes das pol\u00edticas de ScaleDown.", +"message.scaleup.policy.continue": "Por favor, adicione pelo menos uma condi\u00e7\u00e3o \u00e0 pol\u00edtica de ScaleUp para continuar", +"message.scaleup.policy.duration.continue": "Por favor, insira uma dura\u00e7\u00e3o v\u00e1lida para a pol\u00edtica de ScaleUp para continuar", +"message.scaleup.policy.name.continue": "Por favor, insira um nome para a pol\u00edtica de ScaleUp para continuar", "message.select.a.zone": "A zona tipicamente corresponde a um \u00fanico datacenter. M\u00faltiplas zonas auxiliam a nuvem a ser mais confi\u00e1vel provendo isolamento f\u00edsico e redund\u00e2ncia.", "message.select.affinity.groups": "Por favor, selecione quaisquer grupos de afinidade que voc\u00ea deseja que esta VM perten\u00e7a:", +"message.select.bgp.peers": "Por favor, selecione / desmarque os pares BGP associados \u00e0 rede ou VPC:", "message.select.deselect.desired.options": "Por favor, selecione / desselecione as op\u00e7\u00f5es desejadas", +"message.select.deselect.to.sort": "Por favor, selecione / desmarque para ordenar os valores", "message.select.destination.image.stores": "Por favor, selecione o(s) armazenamento(s) de imagem(ns) para os quais os dados devem ser migrados", "message.select.disk.offering": "Por favor, selecione uma oferta de disco para o disco", "message.select.end.date.and.time": "Selecione uma data e hor\u00e1rio final.", +"message.select.kvm.host.instance.conversion": "(Opcional) Selecione um host KVM no cluster para realizar a convers\u00e3o da inst\u00e2ncia atrav\u00e9s do virt-v2v", +"message.select.load.balancer.rule": "Por favor, selecione uma regra de balanceamento de carga para o seu Grupo de escalonamento automatico.", "message.select.migration.policy": "Por favor, selecione uma pol\u00edtica de migra\u00e7\u00e3o", "message.select.nic.network": "Por favor, selecione uma rede para o NIC", "message.select.security.groups": "Por favor selecione o(s) grupo(s) de seguran\u00e7a para sua nova VM", "message.select.start.date.and.time": "Selecione uma data e hor\u00e1rio inicial.", +"message.select.temporary.storage.instance.conversion": "(Opcional) Selecione um destino tempor\u00e1rio de Armazenamento para os discos convertidos atrav\u00e9s do virt-v2v", +"message.select.volume.to.continue": "Por favor, selecione um volume para continuar.", "message.select.zone.description": "Selecione o tipo de zona b\u00e1sica/avan\u00e7ada", "message.select.zone.hint": "Este \u00e9 o tipo de implanta\u00e7\u00e3o de zona que voc\u00ea deseja utilizar. Zona b\u00e1sica: fornece uma \u00fanica rede na qual para cada inst\u00e2ncia de VM \u00e9 atribu\u00eddo um IP diretamente da rede. O isolamento do guest pode ser fornecido atrav\u00e9s de meios da camada 3, tais como grupos de seguran\u00e7a (filtragem de source IP). Zona avan\u00e7ada: para topologias de rede mais sofisticadas. este modelo de rede oferece maior flexibilidade na defini\u00e7\u00e3o de redes guest e oferece ofertas de rede personalizadas, como firewall, VPN, ou suporte de balanceamento de carga.", +"message.select.vm.to.continue": "Por favor, selecione uma inst\u00e2ncia para continuar", +"message.server": "Servidor : ", "message.server.description": "NFS, iSCSI, ou PreSetup: endere\u00e7o IP ou nome DNS do dispositivo de armazenamento. VMWare PreSetup: endere\u00e7o IP ou nome DNS do servidor vCenter. Linstor: URL HTTP(S) do linstor-controller.", "message.set.default.nic": "Por favor confirme que voc\u00ea quer tornar este NIC o padr\u00e3o para esta VM,", "message.set.default.nic.manual": "Por favor atualize manualmente o NIC padr\u00e3o desta VM agora.", "message.setting.updated": "Configura\u00e7\u00e3o atualizada:", +"message.setting.update.delay": "O novo valor entrar\u00e1 em vigor em 30 segundos.", "message.setup.physical.network.during.zone.creation": "Ao adicionar uma zona avan\u00e7ada, \u00e9 preciso configurar uma ou mais redes f\u00edsicas. Cada rede corresponde \u00e0 uma interface de rede no virtualizador. Cada rede f\u00edsica pode ser utilizada para transportar um ou mais tipos de tr\u00e1fego, com certas restri\u00e7\u00f5es sobre como eles podem ser combinados.
Arraste e solte um ou mais tipos de tr\u00e1fego em cada rede f\u00edsica.", "message.setup.physical.network.during.zone.creation.basic": "Ao adicionar uma zona b\u00e1sica, \u00e9 poss\u00edvel configurar uma rede f\u00edsica, que corresponde a uma interface de rede no virtualizador. A rede carrega diversos tipos de tr\u00e1fego.

\u00c9 poss\u00edvel adicionar e remover outros tipos de tr\u00e1fego na mesma interface de rede f\u00edsica.", +"message.shared.network.offering.warning": "Administradores de dom\u00ednio e usu\u00e1rios regulares s\u00f3 podem criar Redes compartilhadas a partir de ofertas de Rede com a configura\u00e7\u00e3o specifyvlan=false. Por favor, contate um administrador para criar uma oferta de Rede se esta lista estiver vazia.", +"message.shared.network.unsupported.for.nsx": "Redes compartilhadas n\u00e3o s\u00e3o suportadas para zonas habilitadas para NSX", +"message.shutdown.triggered": "Um desligamento foi acionado. CloudStack n\u00e3o aceitar\u00e1 novos jobs", +"message.snapshot.additional.zones": "Snapshots sempre ser\u00e3o criados em sua zona nativa - %x, aqui voc\u00ea pode selecionar zona(s) adicional(is) para onde ela ser\u00e1 copiada no momento da cria\u00e7\u00e3o", +"message.sourcenatip.change.inhibited": "Alterar o sourcenat para este IP da Rede para este endere\u00e7o est\u00e1 inibido, pois regras de firewall est\u00e3o definidas para ele. Isso pode incluir regras de encaminhamento de porta ou balanceamento de carga.\n - Se esta for uma Rede Isolada, por favor use updateNetwork/clique no bot\u00e3o editar.\n - Se esta for uma VPC, primeiro limpe todas as outras regras para este endere\u00e7o.", +"message.sourcenatip.change.warning": "AVISO: Alterar o endere\u00e7o IP sourcenat da rede causar\u00e1 tempo de inatividade de conectividade para as Inst\u00e2ncias com NICs na Rede.", "message.specify.tag.key": "Por favor, especifique uma chave de etiqueta", "message.specify.tag.value": "Por favor, especifique um valor para a etiqueta", "message.step.2.continue": "Selecione o plano", @@ -2383,55 +3479,101 @@ "message.step.4.continue": "Selecione pelo menos uma rede para continuar", "message.step.license.agreements.continue": "Por favor, aceite todos os termos de licen\u00e7a para continuar", "message.success.acquire.ip": "IP adquirido com sucesso", +"message.success.add.bgp.peer": "Novo par BGP adicionado com sucesso", "message.success.add.egress.rule": "Nova regra de sa\u00edda adicionada com sucesso", "message.success.add.firewall.rule": "Nova regra do firewall adicionada com sucesso", "message.success.add.guest.network": "Rede guest criada com sucesso", +"message.success.add.interface.static.route": "Rota Est\u00e1tica de interface adicionada com sucesso", +"message.success.add.ip.v6.prefix": "Prefixo IPv6 adicionado com sucesso", "message.success.add.iprange": "Intervalo IP adicionado com sucesso", +"message.success.add.ipv4.subnet": "Sub-rede IPv4 adicionada com sucesso", +"message.success.add.ipv4.subnet.for.guest.network": "Sub-rede IPv4 para rede guest adicionada com sucesso", "message.success.add.kuberversion": "Vers\u00e3o do Kubernetes adicionado com sucesso", +"message.success.add.logical.router": "Roteador L\u00f3gico adicionado com sucesso", "message.success.add.network": "Rede adicionada com sucesso", "message.success.add.network.acl": "Lista ACL da rede adicionada com sucesso", +"message.success.add.network.permissions": "Permiss\u00f5es de Rede adicionadas com sucesso", +"message.success.add.network.static.route": "Rota Est\u00e1tica de Rede adicionada com sucesso", +"message.success.add.object.storage": "Object Storage adicionado com sucesso", +"message.success.add.physical.network": "Rede F\u00edsica adicionada com sucesso", +"message.success.add.policy.rule": "Regra de Pol\u00edtica adicionada com sucesso", +"message.success.add.router.table.to.instance": "Tabela de Roteador adicionada \u00e0 Inst\u00e2ncia com sucesso", "message.success.add.port.forward": "Nova regra de encaminhamento de porta adicionada com sucesso", "message.success.add.private.gateway": "Gateway privado adicionado com sucesso", "message.success.add.rule": "Nova regra adicionada com sucesso", "message.success.add.secondary.ipaddress": "IP secund\u00e1rio adicionado com sucesso", "message.success.add.static.route": "Rota est\u00e1tica adicionada com sucesso", "message.success.add.tag": "Etiqueta adicionada com sucesso", +"message.success.add.tungsten.router.table": "Tabela de Roteador adicionada com sucesso", +"message.success.add.tungsten.routing.policy": "Pol\u00edtica de roteamento Tungsten-Fabric adicionada com sucesso", +"message.success.add.vpc": "Uma Nuvem Privada Virtual adicionada com sucesso", "message.success.add.vpc.network": "Rede VPC adicionada com sucesso", "message.success.add.vpn.customer.gateway": "Gateway da VPN do cliente adicionado com sucesso", "message.success.add.vpn.gateway": "Gateway da VPN adicionado com sucesso", "message.success.assign.vm": "VM atribu\u00edda com sucesso", +"message.success.assign.volume": "Volume atribu\u00eddo com sucesso", +"message.success.apply.network.policy": "Pol\u00edtica de Rede aplicada com sucesso", +"message.success.apply.tungsten.tag": "Tag aplicada com sucesso", +"message.success.asign.vm": "Inst\u00e2ncia atribu\u00edda com sucesso", "message.success.assigned.vms": "VMs atribu\u00eddas com sucesso", "message.success.certificate.upload": "Certificado carregado com sucesso", "message.success.change.affinity.group": "Grupos de afinidade alterados com sucesso", +"message.success.change.bgp.peers": "Pares BGP alterados com sucesso", "message.success.change.offering": "Oferta alterada com sucesso", "message.success.change.password": "Senha alterada com sucesso", "message.success.change.host.password": "Senha do host \"{name}\" foi alterada com sucesso", +"message.success.change.scope": "Escopo alterado com sucesso para o pool de armazenamento", +"message.success.clear.webhook.deliveries": "Entregas de webhook limpas com sucesso", +"message.success.config.health.monitor": "Monitor de Sa\u00fade Configurado com Sucesso", +"message.success.config.vm.schedule": "Agendamento de Inst\u00e2ncia configurado com sucesso", "message.success.config.backup.schedule": "Agendamento de backup de VM configurado com sucesso", "message.success.config.sticky.policy": "Sticky policy configurada com sucesso", "message.success.copy.clipboard": "Copiado com sucesso para a \u00e1rea de transfer\u00eancia", "message.success.create.account": "Conta criada com sucesso", +"message.success.create.asnrange": "Faixa AS criada com sucesso", +"message.success.create.bucket": "Bucket criado com sucesso", +"message.success.create.gui.theme": "Tema da GUI \"{guiTheme}\" criado com sucesso", "message.success.create.internallb": "LB interno criado com sucesso", "message.success.create.isolated.network": "Rede isolada criada com sucesso", "message.success.create.keypair": "Par de chaves SSH criado com sucesso", "message.success.create.kubernetes.cluster": "Cluster Kubernetes criado com sucesso", "message.success.create.l2.network": "Rede L2 criada com sucesso", +"message.success.create.password": "Par de chave de acesso \u00e0 API criada com sucesso", +"message.success.create.sharedfs": "Sistema de Arquivos Compartilhado criado com sucesso", "message.success.create.snapshot.from.vmsnapshot": "Snapshot de volume a partir da snapshot de VM criada com sucesso", +"message.success.create.template": "Template criado com sucesso", "message.success.create.user": "Usu\u00e1rio criado com sucesso", "message.success.create.volume": "Volume criado com sucesso", +"message.success.create.webhook": "Webhook criado com sucesso", +"message.success.dedicate.bgp.peer": "Par BGP dedicado com sucesso", +"message.success.dedicate.ipv4.subnet": "Sub-rede IPv4 dedicada com sucesso", "message.success.delete": "Exclu\u00eddo com sucesso", "message.success.delete.acl.rule": "Regra ACL removida com sucesso", +"message.success.delete.asnrange": "Faixa AS exclu\u00edda com sucesso", +"message.success.delete.backup.schedule": "Agendamento de Backup de Inst\u00e2ncia exclu\u00eddo com sucesso", +"message.success.delete.bgp.peer": "Par BGP exclu\u00eddo com sucesso", "message.success.delete.icon": "\u00cdcone deletado com sucesso", +"message.success.delete.interface.static.route": "Rota Est\u00e1tica de interface removida com sucesso", +"message.success.delete.ipv4.subnet": "Sub-rede IPv4 removida com sucesso", +"message.success.delete.keypair": "Par de chave de API deletada com sucesso", +"message.success.delete.network.static.route": "Rota Est\u00e1tica de Rede removida com sucesso", "message.success.delete.node": "Nodo exclu\u00eddo com sucesso", "message.success.delete.snapshot.policy": "Pol\u00edtica de snapshot exclu\u00edda com sucesso", "message.success.delete.static.route": "Rota est\u00e1tica exclu\u00edda com sucesso", "message.success.delete.tag": "Tag exclu\u00edda com sucesso", +"message.success.delete.tungsten.policy.rule": "Regra de Pol\u00edtica exclu\u00edda com sucesso", +"message.success.delete.tungsten.router.table": "Tabela de Roteador removida com sucesso", +"message.success.delete.tungsten.tag": "Tag removida com sucesso", "message.success.delete.vm": "VM remov\u00edda com sucesso", "message.success.disable.saml.auth": "Autoriza\u00e7\u00e3o SAML desativada com sucesso", "message.success.disable.vpn": "VPN desativada com sucesso", "message.success.edit.acl": "Regra ACL editada com sucesso", +"message.success.edit.primary.storage": "Armazenamento Prim\u00e1rio editado com sucesso", "message.success.edit.rule": "Regra editada com sucesso", "message.success.enable.saml.auth": "Autoriza\u00e7\u00e3o SAML ativada com sucesso", "message.success.import.instance": "Inst\u00e2ncia importada com sucesso", +"message.success.import.volume": "Volume importado com sucesso", +"message.success.migration": "Migra\u00e7\u00e3o conclu\u00edda com sucesso", "message.success.migrate.volume": "Volume migrado com sucesso", "message.success.migrating": "Migra\u00e7\u00e3o conclu\u00edda com sucesso para", "message.success.move.acl.order": "Regra de ACL movida com sucesso", @@ -2439,26 +3581,47 @@ "message.success.register.iso": "ISO registrado com sucesso", "message.success.register.keypair": "Par de chaves SSH registrado com sucesso", "message.success.register.template": "Template cadastrado com sucesso", +"message.success.register.user.data": "Userdata registrado com sucesso", +"message.success.register.user.keypair": "Novo par de chave de API criado com sucesso", +"message.success.release.dedicated.bgp.peer": "Par BGP dedicado liberado com sucesso", +"message.success.release.dedicated.ipv4.subnet": "Sub-rede IPv4 dedicada liberada com sucesso", "message.success.release.ip": "IP liberado com sucesso", "message.success.remove.egress.rule": "Regra de sa\u00edda removida com sucesso", "message.success.remove.firewall.rule": "Regra de firewall removida com sucesso", "message.success.remove.instance.rule": "Inst\u00e2ncia removida da regra", "message.success.remove.ip": "IP removido com sucesso", "message.success.remove.iprange": "Intervalo de IP removido com sucesso", +"message.success.remove.logical.router": "Roteador L\u00f3gico removido com sucesso", +"message.success.remove.network.permissions": "Permiss\u00f5es de Rede removidas com sucesso", +"message.success.remove.network.policy": "Pol\u00edtica de Rede removida com sucesso", "message.success.remove.nic": "NIC removida com sucesso", +"message.success.remove.objectstore.directory": "Diret\u00f3rio selecionado removido com sucesso", +"message.success.remove.objectstore.objects": "Objeto(s) selecionado(s) removido(s) com sucesso", "message.success.remove.port.forward": "Regra de encaminhamento de porta removida com sucesso", +"message.success.remove.router.table.from.interface": "Tabela de Roteador removida da interface com sucesso", "message.success.remove.rule": "Regra exclu\u00edda com sucesso", "message.success.remove.secondary.ipaddress": "Endere\u00e7o de IP secund\u00e1rio removido com sucesso", "message.success.remove.sticky.policy": "Sticky policy removida com sucesso", +"message.success.remove.tungsten.routing.policy": "Pol\u00edtica de Roteamento Tungsten-Fabric removida da Rede com sucesso", +"message.success.reset.network.permissions": "Permiss\u00f5es de Rede redefinidas com sucesso", "message.success.resize.volume": "Volume redimensionado com sucesso", "message.success.scale.kubernetes": "Cluster Kubernetes escalonado com sucesso", "message.success.unmanage.instance": "Inst\u00e2ncia n\u00e3o gerenciada com sucesso", +"message.success.unmanage.volume": "Volume n\u00e3o gerenciado com sucesso", +"message.success.update.account": "Conta atualizada com sucesso", +"message.success.update.bgp.peer": "Par BGP atualizado com sucesso", +"message.success.update.bucket": "Bucket atualizado com sucesso", +"message.success.update.condition": "Condi\u00e7\u00e3o atualizada com sucesso", +"message.success.update.gui.theme": "Tema da GUI \"{guiTheme}\" atualizado com sucesso", "message.success.update.ipaddress": "Endere\u00e7o IP atualizado com sucesso", "message.success.update.iprange": "Intervalo de IP atualizado com sucesso", +"message.success.update.ipv4.subnet": "Sub-rede IPv4 atualizada com sucesso", "message.success.update.kubeversion": "Vers\u00e3o compat\u00edvel com Kubernetes atualizada com sucesso", "message.success.update.network": "Rede atualizada com sucesso", "message.success.update.nic": "NIC atualizada com sucesso", "message.success.update.user": "Usu\u00e1rio atualizado com sucesso", +"message.success.update.sharedfs": "Sistema de Arquivos Compartilhado atualizado com sucesso", +"message.success.update.template": "Template atualizado com sucesso", "message.success.upgrade.kubernetes": "Cluster do Kubernetes atualizado com sucesso", "message.success.upload": "Carregado com sucesso", "message.success.upload.description": "Este arquivo de ISO foi carregado. Verifique seu status no menu Imagens > ISOs. Verifique seu status no menu templates", @@ -2469,15 +3632,43 @@ "message.suspend.project": "Voc\u00ea tem certeza que deseja suspender este projeto?", "message.sussess.discovering.feature": "Descoberto todos os recursos dispon\u00edveis!", "message.switch.to": "Transferido para", +"message.template.arch": "Por favor, selecione uma arquitetura de Template.", "message.template.desc": "Imagem de SO que pode ser utilizada para bootar VMs", "message.template.import.vm.temporary": "Se um template tempor\u00e1rio for usado, a opera\u00e7\u00e3o de redefini\u00e7\u00e3o da VM n\u00e3o funcionar\u00e1 ap\u00f3s a importa\u00e7\u00e3o.", "message.template.iso": "Selecione o template ou ISO para continuar", +"message.template.type.change.warning": "AVISO: Alterar o tipo de Template para SYSTEM desativar\u00e1 futuras altera\u00e7\u00f5es no Template.", +"message.test.webhook.delivery": "Testar entrega para o Webhook com um payload opcional", "message.tooltip.reserved.system.netmask": "O prefixo de rede que define a subrede deste pod. utilize a nota\u00e7\u00e3o CIDR.", +"message.traffic.type.deleted": "Tipo de tr\u00e1fego exclu\u00eddo com sucesso", +"message.trigger.shutdown": "Por favor, confirme que voc\u00ea gostaria de acionar um desligamento neste Management Server. Ele n\u00e3o aceitar\u00e1 nenhum novo job Ass\u00edncrono e terminar\u00e1 ap\u00f3s n\u00e3o haver jobs pendentes.", +"message.two.fa.auth": "Abra o aplicativo de autentica\u00e7\u00e3o de dois fatores no seu dispositivo m\u00f3vel para visualizar seu c\u00f3digo de autentica\u00e7\u00e3o.", +"message.two.fa.auth.register.account": "Abra o aplicativo de autentica\u00e7\u00e3o de dois fatores e escaneie o c\u00f3digo QR para adicionar a Conta de Usu\u00e1rio.", +"message.two.fa.auth.staticpin": "
Voc\u00ea configurou 2FA para verifica\u00e7\u00e3o de seguran\u00e7a.
Insira o PIN est\u00e1tico gerado durante a configura\u00e7\u00e3o do 2FA para verificar.", +"message.two.fa.auth.totp": "
Voc\u00ea configurou 2FA para verifica\u00e7\u00e3o de seguran\u00e7a.
Abra o aplicativo autenticador TOTP no seu dispositivo e insira o c\u00f3digo de autentica\u00e7\u00e3o.", +"message.two.fa.login.page": "A autentica\u00e7\u00e3o de dois fatores (2FA) est\u00e1 ativada na sua Conta, voc\u00ea precisa selecionar um provedor 2FA e configurar. 2FA \u00e9 uma camada extra de seguran\u00e7a para sua conta. Uma vez que a configura\u00e7\u00e3o seja feita, em cada login voc\u00ea ser\u00e1 solicitado a inserir o c\u00f3digo 2FA.
", +"message.two.fa.register.account": "1. Abra o aplicativo autenticador TOTP no seu dispositivo.
2. Escaneie o c\u00f3digo QR abaixo para adicionar o Usu\u00e1rio.
3. Se voc\u00ea n\u00e3o conseguir escanear o c\u00f3digo QR, insira a chave de configura\u00e7\u00e3o manualmente.
4. A verifica\u00e7\u00e3o do c\u00f3digo 2FA \u00e9 obrigat\u00f3ria para completar a configura\u00e7\u00e3o 2FA.", +"message.two.fa.register.account.login.page": "1. Abra o aplicativo autenticador TOTP no seu dispositivo.
2. Escaneie o c\u00f3digo QR abaixo para adicionar o Usu\u00e1rio.
3. Se voc\u00ea n\u00e3o conseguir escanear o c\u00f3digo QR, insira a chave de configura\u00e7\u00e3o manualmente.
4. Verifique o c\u00f3digo 2FA para continuar para o login.", +"message.two.fa.setup.page": "A autentica\u00e7\u00e3o de dois fatores (2FA) \u00e9 uma camada extra de seguran\u00e7a para sua conta.
Uma vez que a configura\u00e7\u00e3o seja feita, em cada login voc\u00ea ser\u00e1 solicitado a inserir o c\u00f3digo 2FA.
", +"message.two.fa.static.pin.part1": "Se voc\u00ea n\u00e3o conseguir escanear o c\u00f3digo QR, ", +"message.two.fa.static.pin.part2": "Clique aqui para visualizar o c\u00f3digo secreto", +"message.two.fa.staticpin": "1. Use o PIN est\u00e1tico gerado como c\u00f3digo 2FA para autentica\u00e7\u00e3o de dois fatores.
2. Salve este PIN est\u00e1tico / c\u00f3digo 2FA e n\u00e3o o compartilhe. Este c\u00f3digo ser\u00e1 usado para logins subsequentes.
3. A verifica\u00e7\u00e3o do c\u00f3digo 2FA \u00e9 obrigat\u00f3ria para completar a configura\u00e7\u00e3o 2FA.", +"message.two.fa.staticpin.login.page": "1. Use o PIN est\u00e1tico gerado como c\u00f3digo 2FA para autentica\u00e7\u00e3o de dois fatores.
2. Salve este PIN est\u00e1tico / c\u00f3digo 2FA e n\u00e3o o compartilhe. Este c\u00f3digo ser\u00e1 usado para logins subsequentes.
3. Verifique o c\u00f3digo 2FA para continuar para o login.", +"message.two.fa.view.setup.key": "Clique aqui para visualizar a chave de configura\u00e7\u00e3o", +"message.two.fa.view.static.pin": "Clique aqui para visualizar o PIN est\u00e1tico", +"message.two.factor.authorization.failed": "Incapaz de verificar 2FA com o c\u00f3digo fornecido, por favor tente novamente.", +"message.type.values.to.add": "Por favor, adicione valores adicionais digitando-os", "message.traffic.type.to.basic.zone": "Tipo de tr\u00e1fego para a zona b\u00e1sica", +"message.update.autoscale.policy.failed": "Falha ao atualizar pol\u00edtica de AutoScale", +"message.update.autoscale.vm.profile.failed": "Falha ao atualizar perfil de Inst\u00e2ncia de AutoScale", +"message.update.autoscale.vmgroup.failed": "Falha ao atualizar grupo de AutoScale", +"message.update.condition.failed": "Falha ao atualizar condi\u00e7\u00e3o", +"message.update.condition.processing": "Atualizando condi\u00e7\u00e3o...", +"message.update.failed": "Atualiza\u00e7\u00e3o falhou", "message.update.nic.processing": "Atualizando NIC...", "message.update.ipaddress.processing": "Atualizando endere\u00e7o IP...", "message.update.resource.count": "Por favor confirme que voc\u00ea quer atualizar a contagem de recursos para esta conta.", "message.update.resource.count.domain": "Por favor, confirme que voc\u00ea deseja atualizar as contagens de recursos para este dom\u00ednio.", +"message.update.resource.limit.max.untagged.error": "Limite n\u00e3o marcado %x \u00e9 %y. Por favor, especifique limite para tag '%z' menor ou igual a esse", "message.update.ssl": "Envie o novo certificado SSL X.509 para ser atualizado em cada console proxy:", "message.upload.failed": "Falha ao fazer o carregamento", "message.upload.file.limit": "Apenas um arquivo pode ser carregado de cada vez", @@ -2492,6 +3683,7 @@ "message.validate.maxlength": "Por favor entre com mais de [0] caracteres.", "message.validate.minlength": "Por favor entre com pelo menos [0] caracteres.", "message.validate.number": "Por favor entre um n\u00famero v\u00e1lido.", +"message.validate.positive.number": "Por favor, insira um n\u00famero positivo v\u00e1lido.", "message.validate.range": "Por favor entre com um valor com valor entre [0] e [1].", "message.validate.range.length": "Por favor entre com um valor com tamanho entre [0] e [1] caracteres.", "message.virtual.router.not.return.elementid": "erro: A API listVirtualRouterElements n\u00e3o retorna o ID do elemento do roteador virtual", @@ -2506,6 +3698,20 @@ "message.vm.state.stopped": "A VM est\u00e1 parada", "message.vm.state.stopping": "A VM est\u00e1 sendo parada", "message.vm.state.unknown": "O estado da VM \u00e9 desconhecido.", +"message.vnf.appliance.networks": "Por favor, selecione as redes para o novo appliance VNF.", +"message.vnf.credentials.change": "Por favor, altere a(s) senha(s) do appliance VNF imediatamente.", +"message.vnf.credentials.default": "A(s) credencial(is) padr\u00e3o do appliance VNF", +"message.vnf.credentials.in.template.vnf.details": "Por favor, encontre as credenciais padr\u00e3o para este VNF nos detalhes do template VNF.", +"message.vnf.error.deviceid.should.be.consecutive": "O deviceid das NICs VNF selecionadas deve ser consecutivo.", +"message.vnf.error.network.is.already.used": "A Rede foi usada por v\u00e1rias NICs do novo appliance VNF.", +"message.vnf.error.network.should.be.used": "Todas as redes selecionadas devem ser usadas como Nics VNF", +"message.vnf.error.no.networks": "Por favor, selecione as redes para o novo appliance VNF.", +"message.vnf.error.no.network.for.required.deviceid": "Por favor, selecione uma Rede para a NIC obrigat\u00f3ria do novo appliance VNF.", +"message.vnf.nic.move.down.fail": "Falha ao mover esta NIC para baixo", +"message.vnf.nic.move.up.fail": "Falha ao mover esta NIC para cima", +"message.vnf.no.credentials": "Nenhuma credencial encontrada para o appliance VNF.", +"message.vnf.select.networks": "Por favor, selecione a rede relevante para cada NIC VNF.", +"message.volume.state.primary.storage.suitability": "A adequa\u00e7\u00e3o de um armazenamento prim\u00e1rio para um volume depende da oferta de disco do volume e da aloca\u00e7\u00e3o da m\u00e1quina virtual (se o volume estiver anexado a uma m\u00e1quina virtual).", "message.volume.state.allocated": "O volume foi alocado, mas ainda n\u00e3o foi criado", "message.volume.state.attaching": "O volume est\u00e1 anexado a um volume do estado pronto.", "message.volume.state.copying": "O volume est\u00e1 sendo copiado do armazenamento de imagens para o armazenamento prim\u00e1rio, no caso de ser um volume carregado", @@ -2525,12 +3731,20 @@ "message.volume.state.uploaderror": "O carregamento do volume encontrou um erro", "message.volume.state.uploadinprogress": "Carregamento do volume em progresso", "message.volume.state.uploadop": "A opera\u00e7\u00e3o de carregamento de volume est\u00e1 em andamento", +"message.volumes.managed": "Volumes controlados pelo CloudStack.", +"message.volumes.unmanaged": "Volumes n\u00e3o controlados pelo CloudStack.", +"message.vpc.restart.required": "Reinicializa\u00e7\u00e3o \u00e9 necess\u00e1ria para VPC(s). Clique aqui para ver VPC(s) que requerem reinicializa\u00e7\u00e3o.", "message.vr.alert.upon.network.offering.creation.others": "Como nenhum dos servi\u00e7os obrigat\u00f3rios para cria\u00e7\u00e3o do VR (VPN, DHCP, DNS, Firewall, LB, UserData, SourceNat, StaticNat, PortForwarding) foram habilitados, o VR n\u00e3o ser\u00e1 criado e a oferta de computa\u00e7\u00e3o n\u00e3o ser\u00e1 usada.", +"message.warn.change.primary.storage.scope": "Este recurso \u00e9 testado e suportado para as seguintes configura\u00e7\u00f5es:
KVM - NFS/Ceph - DefaultPrimary
VMware - NFS - DefaultPrimary
*Pode haver etapas extras envolvidas para faz\u00ea-lo funcionar para outras configura\u00e7\u00f5es.", "message.warn.filetype": "jpg, jpeg, png, bmp e svg s\u00e3o os \u00fanicos formatos de imagem suportados", "message.warn.importing.instance.without.nic": "AVISO: essa inst\u00e2ncia est\u00e1 sendo importada sem NICs e muitos recursos de rede n\u00e3o estar\u00e3o dispon\u00edveis. Considere criar uma NIC antes de importar via VCenter ou assim que a inst\u00e2ncia for importada.", +"message.warn.vpc.offerings": "Ofertas de VPC s\u00c3o exibidas somente caso a conta selecionada possua ao menos uma VPC.", +"message.warn.zone.mtu.update": "Por favor, note que este limite n\u00e3o afetar\u00e1 as configura\u00e7\u00f5es de MTU da Rede pr\u00e9-existentes", +"message.webhook.deliveries.time.filter": "A lista de entregas de webhook pode ser filtrada com base em data e hora. Selecione 'Customizado' para especificar o intervalo de data de in\u00edcio e fim.", "message.zone.creation.complete": "Cria\u00e7\u00e3o de zona completa", "message.zone.detail.description": "Preencha os detalhes da zona", "message.zone.detail.hint": "Uma zona \u00e9 a maior unidade organizacional no CloudStack, e normalmente corresponde a um \u00fanico datacenter. As zonas proporcionam isolamento f\u00edsico e redund\u00e2ncia. Uma zona consiste em um ou mais pods (cada um contendo hosts e servidores de armazenamento prim\u00e1rio) e um servidor de armazenamento secund\u00e1rio que \u00e9 compartilhado por todos os pods da zona.", +"message.zone.edge.local.storage": "Armazenamento local ser\u00e1 usado por padr\u00e3o para Inst\u00e2ncias de Usu\u00e1rio e roteadores virtuais", "message.validate.min": "Por favor entre com um valor maior que ou igual a {0}.", "migrate.from": "Migrar de", "migrate.to": "Migrar para", diff --git a/ui/public/locales/te.json b/ui/public/locales/te.json index ed72626e0a67..f3b4c70ca2ed 100644 --- a/ui/public/locales/te.json +++ b/ui/public/locales/te.json @@ -3169,7 +3169,6 @@ "message.error.zone.name": "దయచేసి జోన్ పేరును నమోదు చేయండి.", "message.error.zone.type": "దయచేసి జోన్ రకాన్ని ఎంచుకోండి.", "message.error.linstor.resourcegroup": "దయచేసి Linstor Resource-Groupని నమోదు చేయండి.", - "message.error.fixed.offering.kvm": "ఫిక్స్‌డ్ కంప్యూట్ ఆఫర్‌తో KVM హైపర్‌వైజర్‌ను ఉపయోగించుకునే సందర్భాలను స్కేల్ చేయడం సాధ్యం కాదు.", "message.error.create.webhook.local.account": "స్థానిక స్కోప్‌తో వెబ్‌హుక్‌ని సృష్టించడానికి తప్పనిసరిగా ఖాతా అందించబడాలి.", "message.error.create.webhook.name": "వెబ్‌హుక్‌ని సృష్టించడానికి తప్పనిసరిగా పేరు అందించాలి.", "message.error.create.webhook.payloadurl": "Webhookని సృష్టించడానికి పేలోడ్ URL తప్పనిసరిగా అందించబడాలి.", diff --git a/ui/public/locales/zh_CN.json b/ui/public/locales/zh_CN.json index 4b68a2ef6d77..9536bab7ee43 100644 --- a/ui/public/locales/zh_CN.json +++ b/ui/public/locales/zh_CN.json @@ -3462,7 +3462,6 @@ "message.error.zone.name": "\u8BF7\u8F93\u5165\u533A\u57DF\u540D\u79F0", "message.error.zone.type": "\u8BF7\u9009\u62E9\u533A\u57DF\u7C7B\u578B", "message.error.linstor.resourcegroup": "\u8BF7\u8F93\u5165 Linstor \u8D44\u6E90\u7EC4", - "message.error.fixed.offering.kvm": "\u4E0D\u53EF\u80FD\u901A\u8FC7\u56FA\u5B9A\u7684\u8BA1\u7B97\u65B9\u6848\u6765\u6269\u5C55KVM\u865A\u62DF\u673A\u3002", "message.fail.to.delete": "\u5220\u9664\u5931\u8D25\u3002", "message.failed.to.add": "\u6DFB\u52A0\u5931\u8D25", "message.failed.to.assign.vms": "\u672A\u80FD\u5206\u914D\u865A\u62DF\u673A", diff --git a/ui/src/components/view/ListView.vue b/ui/src/components/view/ListView.vue index 66dd6b3db9e6..b03293efacae 100644 --- a/ui/src/components/view/ListView.vue +++ b/ui/src/components/view/ListView.vue @@ -949,7 +949,7 @@ style="margin-left: 5px" :actions="actions" :resource="record" - :enabled="quickViewEnabled() && actions.length > 0" + :enabled="quickViewEnabled(actions, columns, column.key)" @exec-action="$parent.execAction" /> diff --git a/ui/src/components/view/WebhookFiltersTab.vue b/ui/src/components/view/WebhookFiltersTab.vue index 2d276e2a3cf0..513720a86258 100644 --- a/ui/src/components/view/WebhookFiltersTab.vue +++ b/ui/src/components/view/WebhookFiltersTab.vue @@ -18,6 +18,7 @@ - +
@@ -270,8 +268,18 @@
- - +
+ + + + + + +
+ + {{ $t('label.ok') }} @@ -686,6 +700,7 @@ export default { confirmDirty: false, firstIndex: 0, modalWidth: '30vw', + actionConfirmText: '', promises: [] } }, @@ -893,6 +908,12 @@ export default { return 'active' } return 'self' + }, + isSubmitDisabled () { + if (this.currentAction?.requireNameConfirmation && !(this.currentAction.groupAction && this.selectedRowKeys.length > 0)) { + return this.actionConfirmText.trim() !== this.resource?.name?.trim() + } + return false } }, methods: { @@ -902,19 +923,6 @@ export default { } return 'inline-flex' }, - getOkProps () { - if (this.selectedRowKeys.length > 0 && this.currentAction?.groupAction) { - } else { - return { props: { type: 'primary' } } - } - }, - getCancelProps () { - if (this.selectedRowKeys.length > 0 && this.currentAction?.groupAction) { - return { props: { type: 'primary' } } - } else { - return { props: { type: 'default' } } - } - }, switchProject (projectId) { if (!projectId || !projectId.length || projectId.length !== 36) { return @@ -1303,6 +1311,7 @@ export default { this.actionLoading = false this.showAction = false this.currentAction = {} + this.actionConfirmText = '' }, cancelAction () { eventBus.emit('action-closing', { action: this.currentAction }) @@ -1360,6 +1369,7 @@ export default { this.currentAction = action this.currentAction.params = store.getters.apis[this.currentAction.api].params this.resource = action.resource + this.actionConfirmText = '' this.$emit('change-resource', this.resource) var paramFields = this.currentAction.params paramFields.sort(function (a, b) { @@ -1374,9 +1384,11 @@ export default { this.currentAction.paramFilters = [] if ('message' in action) { if (typeof action.message === 'function') { - action.message = action.message(action.resource) + action.messageString = action.message(action.resource) + } else { + action.messageString = action.message } - action.message = Array.isArray(action.message) ? this.$t(...action.message) : this.$t(action.message) + action.messageString = Array.isArray(action.messageString) ? this.$t(...action.messageString) : this.$t(action.messageString) } this.getArgs(action, isGroupAction, paramFields) this.getFilters(action, isGroupAction, paramFields) @@ -1642,6 +1654,12 @@ export default { }, handleSubmit (e) { if (this.actionLoading) return + + if (this.currentAction?.requireNameConfirmation && !(this.currentAction.groupAction && this.selectedRowKeys.length > 0)) { + if (this.actionConfirmText.trim() !== this.resource?.name?.trim()) { + return + } + } this.promises = [] if (!this.dataView && this.currentAction.groupAction && this.selectedRowKeys.length > 0) { if (this.selectedRowKeys.length > 0) { diff --git a/ui/src/views/compute/ScaleVM.vue b/ui/src/views/compute/ScaleVM.vue index ce8dca5b43a6..b810e1327c6b 100644 --- a/ui/src/views/compute/ScaleVM.vue +++ b/ui/src/views/compute/ScaleVM.vue @@ -23,10 +23,6 @@ - - - - offering.id === this.resource.serviceofferingid) - this.currentOffer = this.offerings[0] - if (this.currentOffer === undefined) { - this.fixedOfferingKvm = true - } - } - this.offerings.map(i => { this.offeringsMap[i.id] = i }) + this.offerings.forEach(offering => { this.offeringsMap[offering.id] = offering }) }).finally(() => { this.loading = false }) }, getMinCpu () { - // We can only scale up while a VM is running - if (this.resource.state === 'Running') { - return this.resource.cpunumber - } return this.selectedOffering?.serviceofferingdetails?.mincpunumber * 1 || 1 }, - getMinMemory () { - // We can only scale up while a VM is running - if (this.resource.state === 'Running') { - return this.resource.memory + getInitialCpuValue () { + const offeringMinCpu = this.getMinCpu() + if (this.resource.cpunumber < offeringMinCpu) { + return offeringMinCpu } + return this.resource.cpunumber + }, + getMinMemory () { return this.selectedOffering?.serviceofferingdetails?.minmemory * 1 || 32 }, + getInitialMemoryValue () { + const offeringMinMemory = this.getMinMemory() + if (this.resource.memory < offeringMinMemory) { + return offeringMinMemory + } + return this.resource.memory + }, getCPUSpeed () { // We can only scale up while a VM is running if (this.resource.state === 'Running') { diff --git a/ui/src/views/compute/wizard/ComputeSelection.vue b/ui/src/views/compute/wizard/ComputeSelection.vue index 563e17984e35..9cf36153f5cd 100644 --- a/ui/src/views/compute/wizard/ComputeSelection.vue +++ b/ui/src/views/compute/wizard/ComputeSelection.vue @@ -111,6 +111,10 @@ export default { type: Number, default: 0 }, + initialCpuValue: { + type: Number, + default: 0 + }, minCpu: { type: Number, default: 0 @@ -119,6 +123,10 @@ export default { type: Number, default: 2 }, + initialMemoryValue: { + type: Number, + default: 0 + }, minMemory: { type: Number, default: 0 @@ -200,8 +208,8 @@ export default { }, methods: { fillValue () { - this.cpuNumberInputValue = this.minCpu - this.memoryInputValue = this.minMemory + this.cpuNumberInputValue = this.initialCpuValue > 0 ? this.initialCpuValue : this.minCpu + this.memoryInputValue = this.initialMemoryValue > 0 ? this.initialMemoryValue : this.minMemory this.cpuSpeedInputValue = this.cpuSpeed if (!this.preFillContent) { diff --git a/ui/src/views/network/CreateIsolatedNetworkForm.vue b/ui/src/views/network/CreateIsolatedNetworkForm.vue index 7dc4dbe75e3f..3ce8f809a7e2 100644 --- a/ui/src/views/network/CreateIsolatedNetworkForm.vue +++ b/ui/src/views/network/CreateIsolatedNetworkForm.vue @@ -96,6 +96,11 @@ {{ opt.displaytext || opt.name || opt.description }} + + +